Feat: L7 Component 完备性升级 (#1391)

* feat: 1.BaseControl 升级

* build: 将 css 迁移至 less 中

* feat: 1.BaseControl 升级

* fix: l7-utils 添加对 lodash 的依赖

* feat: 1.修复控件布局问题  2.拆分 BaseControl 样式文件 3.新增 Control 文档

* Feat yanxiong merge (#1380)

* feat: 1.新增扫光中心sweepCenter  2.完善扫光Layer配置API和demo

* feat: 1.新增wind图层

* fix: 1.eslint问题

* feat: 1.新增WindLayer 文档和Example

* fix: 1.风场图层文档新增示例图片

* feat: 1.新增轨迹/围墙Example

* feat: 1.新增线图层Demo

* chore: 工程配置优化 (#1278)

* docs: 文档的相关配置

* chore: package json 测试覆盖

* test(source): test

* fix: test ci

* chore: ci

* chore: ci add worker build

* chore: glsl 支持热更新

* chore: test cover view

* chore: ci test

* feat: raisingHeight/heightFixed 补全 (#1280)

* feat: 去除多余 demo、提取图层共用类型定义

* feat: 点图层支持 heightfixed

* feat: point fillImage 支持 raisingHeight

* feat: 点图层 fill、fillImage 补全 raigingHeight\heightFixed

* feat: 点图层 point image 完善对 raisingHeight\heightFixed 的支持

* feat: 修改类型定义

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 优化图片的显示效果 (#1282)

* feat: 优化栅格瓦片的显示效果

* style: lint style

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 1.修复控件布局问题  2.拆分 BaseControl 样式文件 3.新增 Control 文档

* fix: 还原 eslintrc 变更

* Chore test preCommit 增加测试用例校验 (#1281)

* docs: 文档的相关配置

* chore: package json 测试覆盖

* test(source): test

* fix: test ci

* chore: ci

* chore: ci add worker build

* chore: glsl 支持热更新

* chore: test cover view

* chore: ci test

* chore: precommit 增加 test 校验

* chore: remove stroybook lint add dumo demo lint

* chore: pre-commit build 校验

* chore: update ci 触发

* chore: lint 相关配置

* fix: 还原 eslintrc 变更

* feat: 1.新增 ButtonControl 基类  2.完成 Fullscreen UI 开发

* chore: lint check (#1284)

* docs: 文档的相关配置

* chore: package json 测试覆盖

* test(source): test

* fix: test ci

* chore: ci

* chore: ci add worker build

* chore: glsl 支持热更新

* chore: test cover view

* chore: ci test

* chore: precommit 增加 test 校验

* chore: remove stroybook lint add dumo demo lint

* chore: pre-commit build 校验

* chore: update ci 触发

* chore: lint 相关配置

* chore: 合并ci 任务

* fix: lint error

* fix: lint command

* chore: ci test & lint

* feat: 新增枚举类型的色带 (#1283)

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* Chore: 修复部分lint 警告,pre-commit 移除build命令 (#1285)

* chore: lint unuse

* chore: pre commit command

* chore: pre-commit 去除build 命令

* feat: add test case (#1286)

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 完善 ButtonControl 基类

* feat: 修改 simple line 的网格构建和渲染方式 (#1288)

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 新增 popperControl 控件基类

* feat: 完成 popper UI测逻辑

* Chore: lint warn 移除未使用的import (#1287)

* chore: lint unuse

* chore: pre commit command

* chore: pre-commit 去除build 命令

* fix: 修复unuse import

* feat: 1.完善 popper 自动判定位置能力

* feat: 1. SelectControl 开发完成

* fix: 修复丢失代码

* chore: 去除无用依赖

* fix: 修复 PopperControl trigger 为 hover 时,气泡消失异常问题

* fix: 去除选中后关闭气泡的配置项

* fix: 1. Popper 参数 closeOther => unique  2. 补充方法注释

* feat: 1.新增 PopperControl 和 ButtonControl 的 setOptions 方法  2.完成 Logo 组件的升级开发

* feat: 完成 fullscreen 组件的开发

* feat: 适量图层支持透传 transfrom (#1294)

* feat: 矢量图层支持 join/transfrom 透传

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* test: 测试用例支持 new scene 对象 (#1291)

* chore: lint unuse

* chore: pre commit command

* chore: pre-commit 去除build 命令

* fix: 修复unuse import

* test: 增加gl 模拟能力

* fix: lint error

* chore: 提取测试utils 为单独包

* chore: 调整测试覆盖率值

* feat: support simple line vector layer (#1295)

* feat: 矢量图层支持 join/transfrom 透传

* style: lint style

* feat: support simple line vector tile layer

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 提供线图层偏移点位计算的通用方法 (#1293)

* feat: 增加线偏移点的计算

* style: lint style

* feat: 支持对 greatcircle 的偏移点位的计算

* feat: 完善弧线偏移点的 featureId 过滤

* style: lint style

* style: lint style

* feat: 支持对普通线偏移点的计算支持

* feat: 增加线图层偏移点位计算的测试用例

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* chore: update version 2.9.23 -> 2.9.24 (#1296)

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* chore: change publish config (#1297)

* chore: update version 2.9.23 -> 2.9.24

* chore: change test-utils packages.json

* chore: change @antv/l7-test-utils into private

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 1.完成截图组件开发

* feat: 1.完成定位组件的开发

* feat: 兼容不同amapjs 加载方式 (#1265)

* feat: 1.完成定位组件的开发

* feat: 新增 LayerControl 组件

* chore: 删除无用 Example

* fix: 修复 AMap 初始化问题

* fix: 1.锁死 screenfull 版本  2.修复 fullscreen 的按钮默认文本和样式问题

* feat: 1. Fullscreen 构造器中对当前环境是否支持全屏进行监测

* feat: 1.LayerService 中图层发生变动时触发 layerChange 事件

* feat: LayerControl 监听 LayerService 图层发生变更时的事件

* fix: 修复加载高德地图判断加载导致的缓存问题 (#1301)

* fix: 去除 amap 加载的判断

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 矢量图层支持 geojson-vt (#1302)

* feat: 矢量图层支持 geojson-vt

* feat: 优化 parser 类型获取、内置 sourceLayer

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* docs: scale demo & IE 兼容 (#1304)

* docs: 增加demo,关闭 any lint

* docs: demo& ie 兼容问题

* chore: dev-build

* feat: Scale 宽度发生变化时添加渐变效果

* feat: 控件 MapStyleControl => MapTheme

* fix: heatmap render error (#1307)

* fix: 修复热力图渲染失效

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* chore: father 配置 glsl 内联 (#1305)

* docs: 增加demo,关闭 any lint

* docs: demo& ie 兼容问题

* chore: dev-build

* chore: father 配置 glsl 内联

* chore: update version 2.9.24 -> 2.9.25 (#1308)

* fix: 修复矢量图层判断错误

* chore: update version 2.9.24 -> 2.9.25

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 新增 MapTheme 切换主题控件

* test: 新增marker测试用例 (#1309)

Co-authored-by: Dreammy23 <echo.cmy@antgroup.com>

* feat: source 支持 json 下新增 geometry 字段 (#1312)

* feat: source 支持 json 下 geometry 解析

* refactor: 优化循环逻辑

* fix: 解决聚合情况下清空图层点数据未清空BUG (#1311)

* fix: 修复 feature scale 可能存在的 source 取值问题 (#1315)

* fix: 修复 featureScale 错误

* style: lint style

* fix: 修复 feature scale 映射问题

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* fix: 修复栅格图片瓦片的混合问题 (#1316)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* fix: 修复栅格图片瓦片的混合问题

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 新增 Control 相关单测

* feat: 矢量文本计算优化、性能优化 (#1310)

* feat: 新增测试瓦片图层

* style: lint style

* feat: 矢量文本图层性能优化

* chore: change tiletestlayer demo

* style: lint style

* feat: 封装 TileDebugLayer 的source 模块,优化图层默认数据的配置,测试图层样式调整

* style: line style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* [chores] Remove unused dependencie (#1314)

* feat: 自定义图层 - marker&markerLayer - 多个marker节点性能优化 (#1300)

* chore: 新增marker开发demo

* style: 增加格式化空格

* marker性能优化 - 缓存计算变量:ignore (#1298)

Co-authored-by: linlb <linlb@homeking365.com>

* feat: 自定义图层 - marker&markerLayer - markerLayer多节点kmarker性能优化

* feat: 添加demo

* feat: 删除冗余代码

Co-authored-by: Dreammy23 <echo.cmy@antgroup.com>
Co-authored-by: bolry <909559682@qq.com>
Co-authored-by: linlb <linlb@homeking365.com>

* feat: 新增 MouseLocation 组件

* chore: add demo (#1319)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* chore: add demo

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* chore: update version 2.9.25 -> 2.9.26 (#1318)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* chore: update version 2.9.25 -> 2.9.26

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 修复光标经纬度不更新问题

* feat: 1.完善 Zoom 组件 2.去除旧 LayerControl 对应样式代码

* feat: 1.完善 Scale 组件升级

* test: 1.完善 LayerControl 单测

* feat: 调整 Popup 目录结构

* fix: 修复弹框类型组件隐藏时未把弹框隐藏问题

* chore: 增加覆盖率Ci (#1323)

* chore: 增加覆盖率Ci

* chore: jest locv

* fix: layer 单测样例

* fix: SelectControl 样式字体缩小

* fix: 修复 ExportImage 参数 imageType 为 jpg 不生效问题

* fix: 完善 setOptions 方法

* feat: 完成 Popup 的基础拆分

* feat: 气泡 Popup 新增互斥开关 autoClose

* feat: 气泡 Popup 新增配置按 ESC 关闭 Popup

* feat: 气泡 Popup 支持设置自定义 className 和 style

* feat: 气泡 Popup 支持设置自定义 className 和 style

* feat: 优化 Popup less 样式

* fix: 修复gaodev2下MarkerLayer清除有误问题 (#1324)

* fix: markerLayer在GaodeMapV2做底图时removeMarkerLayer视图图层层级改变时会复原被删除的图层 (#1322)

去除clear方法中调用注销监听事件

* fix: 修复gaodev2下layer清除有误问题

Co-authored-by: bolry <909559682@qq.com>
Co-authored-by: Dreammy23 <echo.cmy@antgroup.com>

* docs: remove website demos (#1328)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* docs: remove demos

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 完善 Popup 能力和代码注释

* docs: 移除地球模式点图层demo (#1330)

Co-authored-by: Dreammy23 <echo.cmy@antgroup.com>

* feat: 完善 Popup setOptions 方法

* test: 补充 Popup 单测文件

* feat: 初始化 LayerPopup

* feat: 气泡 Popup 新增气泡标题 title 配置

* fix: 默认source 配置 (#1331)

* fix: 修改source 为空的问题

* chore: 添加默认渲染

* feat: 完成 LayerPopup 组件的开发

* test: 补充 LayerPopup 相关的单测

* fix: source empty err (#1332)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* fix: fix empty source bug

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* fix: 自定义图层 - markerLayer - 修复图层执行clear后聚合能力失效问题 (#1333)

Co-authored-by: Dreammy23 <echo.cmy@antgroup.com>

* docs: website remove mapbox demos (#1334)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* feat: website remove mapbox demos

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* Feat custom map (#1326)

* chore(map): 地图模块重构

* feat: lealetMap for l7

* chore: amap baseservice 重构

* fix: 修改amap 样式

* fix: 单词拼写

* docs: remove website docs (#1336)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* feat: website remove mapbox demos

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 将 iconfont 引入方式从 css => svg

* build: worker 内联打包添补 (#1338)

* feat: 1.场景 Scene 新增 boxSelect 能力

* feat: 组件层适配 gatsby 改造

* docs: 新增 Control 和 Logo 控件文档

* fix: 按照交互稿修复组件样式

* feat: 瓦片图层、地图图层渲染性能/体验优化 (#1329)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* feat: 优化简单矢量线瓦片的计算

* feat: 优化简单线图层的网格计算构建

* style: lint style

* style: lint style

* feat: 矢量瓦片更新渲染优化

* fix: 修复 tileLayer 重复创建导致的瓦片更新错误

* feat: 优化矢量瓦片图层本身的性能

* style: lint style

* feat: 优化 reRender 的调用

* feat: 合并瓦片销毁时的重绘

* feat: 去除矢量文本图层的 remapping 映射

* feat: 网格构建异步改造修复

* feat: 瓦片渲染流程优化

* feat: 通用瓦片流程的优化(主线程阻塞优化)

* feat: 补全瓦片更新触发

* feat: 默认顶点属性构建的优化

* style: lint style

* feat: 调整矢量点 uniform 参数

* chore: 去除矢量图层对偏移坐标的支持(不统一)

* style: lint style

* feat: 合并不同瓦片图层触发的重绘

* style: lint style

* chore: 调整瓦片代码结构

* feat: 矢量图层初始化优化

* chore: code clean

* feat: 矢量图层地图绘制数据属性映射优化

* chore: lint style

* feat: 矢量图层地图绘制初始化优化

* feat: maskLayer 初始化优化、debugtestLayer 默认为 basemap 模式

* chore: style

* feat: 绘制指令优化 - picking drawCommand

* style: lint style

* feat: 优化矢量图层初始化资源的创建

* feat: 简化矢量瓦片图层加载完成触发的重绘

* chore: 统一地图图层的样式写法 color、size

* style: lint style

* style: lint style

* style: lint style

* feat: 瓦片渲染执行优化
 Please enter the commit message for your changes. Lines starting

* feat: 优化拾取渲染

* style: lint style

* chore: style change

* style: lint style

* feat: layer plugin list clean

* style: lint style

* feat: 合并shader 使用

* style: lint style

* feat: 优化 source 计算

* feat: 去除 source 中对创建 tileset 的多余判断

* chore: 优化代码写法

* feat: 地图瓦片图层类型定义优化

* chore: data clean

* style: lint style

* style: lint style

* feat: debugLayer add basemap attr

* chore: demo 调整

* feat: 优化瓦片图层的渲染

* feat: 修改瓦片显示更新

* fix: 修复动画模式传值导致的显示效果问题

* fix: 修复 mapbox version 设置错误的问题

* fix: 修复 CanvasLayer render

* style: lint style

* chore: clean citybuilding demo

* style: lint style

* chore: clean point simple code

* chore: clean point text iconfont

* chore: clean polygon fill code

* style: lint style

* chore: clean billboard demo

* chore: clean polygon water/ocean demos

* chore: clean point radar demos

* chore: clean point normal demos

* chore: clean wind layer demos

* chore: clean demos

* feat: clean demos & fix half line insert attri

* style: lint style

* chore: clean mask demo

* chore: adjust website demo

* style: lint style

* chore: clean worker demos

* style: lint style

* fix: 修复箭头顶点重复插入的问题

* fix: 修复线图层弧线的纹理分布

* chore: website demos code clean

* chore: layerService/renderlayers move clear place

* feat: 优化图片瓦片的颜色映射

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* chore: fix spelling mistake (#1344)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* chore: fix spelling mistake

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 瓦片性能和代码优化 (#1347)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* feat: 优化数据栅格瓦片的渲染、瓦片管理流程完善

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: add Hill shade demo (#1349)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* feat: add hillShade demo - openlayers

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 设置瓦片多服务重构、支持加载多文件 (#1350)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* feat: 优化简单矢量线瓦片的计算

* feat: 优化简单线图层的网格计算构建

* style: lint style

* style: lint style

* feat: 矢量瓦片更新渲染优化

* fix: 修复 tileLayer 重复创建导致的瓦片更新错误

* feat: 优化矢量瓦片图层本身的性能

* style: lint style

* feat: 优化 reRender 的调用

* feat: 合并瓦片销毁时的重绘

* feat: 去除矢量文本图层的 remapping 映射

* feat: 网格构建异步改造修复

* feat: 瓦片渲染流程优化

* feat: 通用瓦片流程的优化(主线程阻塞优化)

* feat: 补全瓦片更新触发

* feat: 默认顶点属性构建的优化

* style: lint style

* feat: 调整矢量点 uniform 参数

* chore: 去除矢量图层对偏移坐标的支持(不统一)

* style: lint style

* feat: 合并不同瓦片图层触发的重绘

* style: lint style

* chore: 调整瓦片代码结构

* feat: 矢量图层初始化优化

* chore: code clean

* feat: 矢量图层地图绘制数据属性映射优化

* chore: lint style

* feat: 矢量图层地图绘制初始化优化

* feat: maskLayer 初始化优化、debugtestLayer 默认为 basemap 模式

* chore: style

* feat: 绘制指令优化 - picking drawCommand

* style: lint style

* feat: 优化矢量图层初始化资源的创建

* feat: 简化矢量瓦片图层加载完成触发的重绘

* chore: 统一地图图层的样式写法 color、size

* style: lint style

* style: lint style

* style: lint style

* feat: 瓦片渲染执行优化
 Please enter the commit message for your changes. Lines starting

* feat: 优化拾取渲染

* style: lint style

* chore: style change

* style: lint style

* feat: layer plugin list clean

* style: lint style

* feat: 合并shader 使用

* style: lint style

* feat: 优化 source 计算

* feat: 去除 source 中对创建 tileset 的多余判断

* chore: 优化代码写法

* feat: 地图瓦片图层类型定义优化

* chore: data clean

* style: lint style

* style: lint style

* feat: debugLayer add basemap attr

* chore: demo 调整

* feat: 优化瓦片图层的渲染

* feat: 修改瓦片显示更新

* fix: 修复动画模式传值导致的显示效果问题

* fix: 修复 mapbox version 设置错误的问题

* fix: 修复 CanvasLayer render

* style: lint style

* chore: clean citybuilding demo

* style: lint style

* chore: clean point simple code

* chore: clean point text iconfont

* chore: clean polygon fill code

* style: lint style

* chore: clean billboard demo

* chore: clean polygon water/ocean demos

* chore: clean point radar demos

* chore: clean point normal demos

* chore: clean wind layer demos

* chore: clean demos

* feat: clean demos & fix half line insert attri

* style: lint style

* chore: clean mask demo

* chore: adjust website demo

* style: lint style

* chore: clean worker demos

* style: lint style

* fix: 修复箭头顶点重复插入的问题

* fix: 修复线图层弧线的纹理分布

* chore: website demos code clean

* chore: layerService/renderlayers move clear place

* chore: update version 2.9.26 -> 2.9.27-alpha.0

* chore: update version 2.9.27-alpha.0 -> 2.9.27-alpha.3

* feat: 支持 multi raster tile

* feat: add hillshade demo

* feat: 瓦片服务地址设置重构、多文件请求代码优化

* chore: 案例瓦片多服务用法修改

* chore: 类型定义优化 & api rename

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* fix: demo address err (#1351)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* docs: 完善官方文档

* feat: 优化瓦片金字塔的计算 (#1355)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* feat: opmitize tile cal

* style: lint style

* chore: update api name

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* fix: 修复更新图层属性 bug (#1356)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* chore: add fix demo

* fix: 修复样式更新的状态问题

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* chore: update version 2.9.27 -> 2.9.28 (#1357)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* chore: update version 2.9.27 -> 2.9.28

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* fix: 修复 layer model 更新时候存在闪烁的问题 (#1358)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* fix: 优化 layer model 的更新链路,避免闪烁

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* chore: update version 2.9.28 -> 2.9.29 (#1360)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* docs: change website url

* chore: update version 2.9.28 -> 2.9.29

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: add l7-three father umd build config (#1361)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* feat: add l7-three father/umd

* feat: remove depecenied lib

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* chore: update version 2.9.29 -> 2.9.30 (#1363)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* chore: update version 2.9.29 -> 2.9.30

* chore: add three ignorefile

* chore: update version 2.9.30 -> 2.9.31

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* fix: 瓦片事件监听失效 (#1365)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* fix: 修复瓦片图层事件失效

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* docs: 完善 L7 组件文档和示例

* feat: 合并代码

* feat: 合并代码

* fix: 修复代码合并丢失代码

* fix: 修改 gcoord 的依赖类型

* fix: 修复 fillShade 文件名称大小写问题

* fix: 修复拾取高亮状态异常 (#1368)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* fix: 修复拾取高亮状态异常

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* fix: font 加载逻辑,添加font 加载完成事件 (#1364)

* fix: 文件名大小写

* fix: font load 逻辑

* fix: font 加载问题

* chore: update version 2.9.31 -> 2.9.32 (#1373)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* chore: update version 2.9.31 -> 2.9.32

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* fix: 修复 setData 引发的高德2 图层抖动问题 (#1376)

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* Fix: css 打包方式内联 (#1375)

* fix: 文件名大小写

* fix: font load 逻辑

* fix: font 加载问题

* fix: lint error

* fix: css lib 打包配置

* docs: 补充 L7 Component 文档相关

* fix: 修复当前 lerna 打包顺序导致报错的问题

* fix: 修复 source 模块 parser 为 Json geometry 情况数据拾取问题 (#1378)

* chore: publish alpha version 2.9.32-alpha.2

* feat: 栅格瓦片的多波段计算 (#1367)

* fix: 修复 featureScale 错误

* style: lint style

* fix: remove featureScalePlugin async

* feat: 增加多波段瓦片的 operation 操作

* style: lint style

* feat: 增加波段指定逻辑

* style: lint style

* feat: 优化多波段的请求操作路径代码

* style: lint style

* feat: multi raster layer support express operation 1.0

* style: lint style

* feat: 增加栅格图层的多波段分析能力

* chore: add raster layer demo

* style: lint style

* style: lint style

* style: lint style

* style: lint style

* style: lint style

* feat: 补充栅格数据表达式计算

* chore: 调整栅格计算方法的位置

* chore: 优化栅格计算代码结构

* chore: 优化数据栅格代码

* style: lint style

* style: lint style

* style: lint style

* feat: band operation handle empty data

* feat: 支持彩色多通道栅格

* feat: 数据栅格瓦片支持彩色多通道波段计算

* style: lint style

* style: lint style

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* fix: 解决 turf 依赖问题

* feat: 升级版本号

* feat: 升级版本号

* chore: update version 2.9.32 -> 2.9.33 (#1379)

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 修复 IControlOption 报错问题

* fix: css lib (#1381)

* chore: 更新版本 2.9.34 (#1383)

Co-authored-by: 象数 <zhengxue.lzx@antgroup.com>

* fix: 优化多图层 setData 效果不同步的现象 (#1384)

* fix: 优化多图层 setData 效果不同步的现象

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 新增矢量瓦片类型 - 掩模图层 (#1382)

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* chore: update version 2.9.34 -> 2.9.35 (#1385)

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* fix: 修复设置地图中心点引发的问题 (#1386)

* fix: 修复设置地图中心点引发的问题

* style: lint style

* feat: 补充空值时图层绘制采用默认地图中心点

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* chore: update version 2.9.35 -> 2.9.36 (#1387)

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>

* feat: 去除 Control 中 abstract 的描述

Co-authored-by: yanxiong <oujinhui.ojh@antgroup.com>
Co-authored-by: @thinkinggis <lzx199065@gmail.com>
Co-authored-by: YiQianYao <42212176+yiiiiiiqianyao@users.noreply.github.com>
Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>
Co-authored-by: qxang <qxkang@126.com>
Co-authored-by: Dreammy23 <caomengyuan2015@163.com>
Co-authored-by: Dreammy23 <echo.cmy@antgroup.com>
Co-authored-by: lvisei <yunji.me@outlook.com>
Co-authored-by: MarkLei7 <33211377+MarkLei7@users.noreply.github.com>
Co-authored-by: Jeffrey <color.dove@gmail.com>
Co-authored-by: bolry <909559682@qq.com>
Co-authored-by: linlb <linlb@homeking365.com>
Co-authored-by: 象数 <zhengxue.lzx@antgroup.com>

* feat: 去除 selectBoxClassName 属性

* feat: 将 Control Option 接口定义放到 @antv/l7-component 中

* fix: 将 l7-scene 中的 turf 方法转移到 l7-utils 中

* fix: 添加对 BoxSelect 选择归宿子包的 TODO

* feat: 替换 Control Iconfont 图标

* feat: 组件中涉及到的图标支持传输 Fragment 类型

* feat: 组件层 Control 更新配置时需要传入默认配置

Co-authored-by: yanxiong <oujinhui.ojh@antgroup.com>
Co-authored-by: @thinkinggis <lzx199065@gmail.com>
Co-authored-by: YiQianYao <42212176+yiiiiiiqianyao@users.noreply.github.com>
Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>
Co-authored-by: qxang <qxkang@126.com>
Co-authored-by: Dreammy23 <caomengyuan2015@163.com>
Co-authored-by: Dreammy23 <echo.cmy@antgroup.com>
Co-authored-by: lvisei <yunji.me@outlook.com>
Co-authored-by: MarkLei7 <33211377+MarkLei7@users.noreply.github.com>
Co-authored-by: Jeffrey <color.dove@gmail.com>
Co-authored-by: bolry <909559682@qq.com>
Co-authored-by: linlb <linlb@homeking365.com>
Co-authored-by: 象数 <zhengxue.lzx@antgroup.com>
This commit is contained in:
heiyexing 2022-10-17 17:01:52 +08:00 committed by GitHub
parent 050da8bc72
commit d4c1921b1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
191 changed files with 8308 additions and 1899 deletions

View File

@ -0,0 +1,6 @@
---
title: 导出图片
order: 9
---
<code src="./exportImage.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,86 @@
import { GaodeMapV2, Scene, ExportImage, PointLayer } from '@antv/l7';
import React, { useState } from 'react';
// tslint:disable-next-line:no-duplicate-imports
import { FunctionComponent, useEffect } from 'react';
const Demo: FunctionComponent = () => {
const [scene, setScene] = useState<Scene | undefined>();
const [imgSrc, setImgSrc] = useState('');
const [control, setControl] = useState<ExportImage | null>(null);
useEffect(() => {
const newScene = new Scene({
id: 'map',
map: new GaodeMapV2({
style: 'normal',
center: [120, 30],
pitch: 0,
zoom: 6.45,
WebGLParams: {
preserveDrawingBuffer: true,
},
}),
// logoVisible: false,
});
newScene.on('loaded', () => {
const newControl = new ExportImage({
onExport: (base64) => {
setImgSrc(base64);
},
});
newScene.addControl(newControl);
setControl(newControl);
fetch(
'https://gw.alipayobjects.com/os/basement_prod/d3564b06-670f-46ea-8edb-842f7010a7c6.json',
)
.then((res) => res.json())
.then((data) => {
const pointLayer = new PointLayer({
autoFit: true,
})
.source(data)
.shape('circle')
.size('mag', [1, 25])
.color('mag', (mag) => {
return mag > 4.5 ? '#5B8FF9' : '#5CCEA1';
})
.active(true)
.style({
opacity: 0.3,
strokeWidth: 1,
});
newScene.addLayer(pointLayer);
setScene(newScene);
});
});
}, []);
return (
<>
<button
onClick={() => {
control?.setOptions({
imageType: 'jpeg',
});
}}
>
JPG
</button>
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
<div>
<div></div>
<img src={imgSrc} style={{ width: 200, height: 100 }} />
</div>
</>
);
};
export default Demo;

View File

@ -0,0 +1,6 @@
---
title: 全屏
order: 8
---
<code src="./fullscreen.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,38 @@
import { GaodeMap, Scene, Fullscreen } from '@antv/l7';
import React from 'react';
// tslint:disable-next-line:no-duplicate-imports
import { FunctionComponent, useEffect } from 'react';
const Demo: FunctionComponent = () => {
useEffect(() => {
const scene = new Scene({
id: 'map',
map: new GaodeMap({
style: 'normal',
center: [120, 30],
pitch: 0,
zoom: 6.45,
}),
// logoVisible: false,
});
scene.on('loaded', () => {
const newFullscreen = new Fullscreen();
scene.addControl(newFullscreen);
});
}, []);
return (
<>
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
</>
);
};
export default Demo;

View File

@ -0,0 +1,6 @@
---
title: 显示/隐藏
order: 3
---
<code src="./hide.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,59 @@
import { GaodeMap, PositionType, Scene, MapTheme } from '@antv/l7';
import React, { useState } from 'react';
// tslint:disable-next-line:no-duplicate-imports
import { FunctionComponent, useEffect } from 'react';
const POSITION_LIST = Object.values(PositionType);
const Demo: FunctionComponent = () => {
const [zoom, setZoom] = useState(() => {
return new MapTheme({
position: 'topleft',
});
});
useEffect(() => {
const scene = new Scene({
id: 'map',
map: new GaodeMap({
style: 'dark',
center: [120, 30],
pitch: 0,
zoom: 6.45,
}),
// logoVisible: false,
});
scene.on('loaded', () => {
scene.addControl(zoom);
});
}, []);
return (
<>
<button
onClick={() => {
zoom.show();
}}
>
</button>
<button
onClick={() => {
zoom.hide();
}}
>
</button>
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
</>
);
};
export default Demo;

View File

@ -0,0 +1,6 @@
---
title: 图层控制
order: 13
---
<code src="./layerControl.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,148 @@
import {
GaodeMapV2,
Scene,
LayerControl,
ILayer,
PointLayer,
LineLayer,
PolygonLayer,
} from '@antv/l7';
import React, { useState } from 'react';
// tslint:disable-next-line:no-duplicate-imports
import { FunctionComponent, useEffect } from 'react';
const Demo: FunctionComponent = () => {
const [layers, setLayers] = useState<ILayer[]>([]);
const [scene, setScene] = useState<Scene | undefined>();
const [newLayer, setNewLayer] = useState<ILayer | null>(null);
const [control, setControl] = useState<LayerControl | null>(null);
useEffect(() => {
const newScene = new Scene({
id: 'map',
map: new GaodeMapV2({
style: 'normal',
center: [120, 30],
pitch: 0,
zoom: 6.45,
}),
// logoVisible: false,
});
newScene.on('loaded', () => {
const newLayers: ILayer[] = [];
window.Promise.all([
fetch(
'https://gw.alipayobjects.com/os/basement_prod/d3564b06-670f-46ea-8edb-842f7010a7c6.json',
)
.then((res) => res.json())
.then((data) => {
const pointLayer = new PointLayer({
name: '点图层',
autoFit: true,
})
.source(data)
.shape('circle')
.size('mag', [1, 25])
.color('mag', (mag) => {
return mag > 4.5 ? '#5B8FF9' : '#5CCEA1';
})
.active(true)
.style({
opacity: 0.3,
strokeWidth: 1,
});
setNewLayer(pointLayer);
newScene.addLayer(pointLayer);
}),
fetch(
// 'https://gw.alipayobjects.com/os/bmw-prod/1981b358-28d8-4a2f-9c74-a857d5925ef1.json' // 获取行政区划P噢利用
'https://gw.alipayobjects.com/os/bmw-prod/d6da7ac1-8b4f-4a55-93ea-e81aa08f0cf3.json',
)
.then((res) => res.json())
.then((data) => {
const chinaPolygonLayer = new PolygonLayer({
name: '中国填充',
autoFit: true,
})
.source(data)
.color('name', [
'rgb(239,243,255)',
'rgb(189,215,231)',
'rgb(107,174,214)',
'rgb(49,130,189)',
'rgb(8,81,156)',
])
.shape('fill')
.style({
opacity: 1,
});
// 图层边界
const layer2 = new LineLayer({
name: '中国边框',
zIndex: 2,
})
.source(data)
.color('rgb(93,112,146)')
.size(0.6)
.style({
opacity: 1,
});
layer2.hide();
newScene.addLayer(chinaPolygonLayer);
newScene.addLayer(layer2);
newLayers.push(chinaPolygonLayer, layer2);
}),
]).then(() => {
const newControl = new LayerControl({
layers: newLayers,
});
setControl(newControl);
newScene.addControl(newControl);
setLayers(newLayers);
setScene(newScene);
});
});
}, []);
return (
<>
<button
onClick={() => {
const layer = layers[0];
if (layer?.isVisible()) {
layer?.hide();
} else {
layer?.show();
}
}}
>
</button>
<button
onClick={() => {
if (newLayer && control && !layers.includes(newLayer)) {
const newLayers = [...layers, newLayer];
control?.setOptions({
layers: newLayers,
});
setLayers(newLayers);
}
}}
>
</button>
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
</>
);
};
export default Demo;

View File

@ -0,0 +1,6 @@
---
title: Logo
order: 10
---
<code src="./logo.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,73 @@
import { GaodeMap, Scene, Logo } from '@antv/l7';
import React from 'react';
// tslint:disable-next-line:no-duplicate-imports
import { FunctionComponent, useEffect } from 'react';
const Demo: FunctionComponent = () => {
useEffect(() => {
const scene = new Scene({
id: 'map',
map: new GaodeMap({
style: 'dark',
center: [120, 30],
pitch: 0,
zoom: 6.45,
}),
logoVisible: false,
});
scene.on('loaded', () => {
const logo1 = new Logo({
position: 'leftbottom',
});
scene.addControl(logo1);
setTimeout(() => {
logo1.setOptions({
img:
'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
href: '',
style: 'height: 40px; width: 40px;',
});
}, 1000);
const logo2 = new Logo({
position: 'rightbottom',
href: undefined,
});
scene.addControl(logo2);
const logo3 = new Logo({
position: 'topright',
style: 'height: 40px; width: 40px;',
img:
'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
href: '',
});
scene.addControl(logo3);
const logo4 = new Logo({
position: 'topleft',
style: 'height: 40px; width: 40px;',
img:
'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
href: 'https://ant.design/index-cn',
});
scene.addControl(logo4);
});
}, []);
return (
<>
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
</>
);
};
export default Demo;

View File

@ -0,0 +1,6 @@
---
title: 地图样式
order: 12
---
<code src="./mapTheme.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,53 @@
import { GaodeMap, Scene, MapTheme } from '@antv/l7';
import React, { useState } from 'react';
// tslint:disable-next-line:no-duplicate-imports
import { FunctionComponent, useEffect } from 'react';
const Demo: FunctionComponent = () => {
const [scene, setScene] = useState<Scene | undefined>();
useEffect(() => {
const newScene = new Scene({
id: 'map',
map: new GaodeMap({
center: [120, 30],
pitch: 0,
zoom: 6.45,
style: 'normal',
}),
// map: new GaodeMapV2({
// style: 'dark',
// center: [120, 30],
// pitch: 0,
// zoom: 6.45,
// }),
// map: new GaodeMap({
// style: 'dark',
// center: [120, 30],
// pitch: 0,
// zoom: 6.45,
// }),
});
newScene.on('loaded', () => {
const newControl = new MapTheme({
// defaultValue: 'normal',
});
newScene.addControl(newControl);
});
}, []);
return (
<>
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
</>
);
};
export default Demo;

View File

@ -0,0 +1,6 @@
---
title: 鼠标经纬度
order: 12
---
<code src="./mouseLocation.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,42 @@
import { GaodeMapV2, Scene, MouseLocation } from '@antv/l7';
import React from 'react';
// tslint:disable-next-line:no-duplicate-imports
import { FunctionComponent, useEffect } from 'react';
const Demo: FunctionComponent = () => {
useEffect(() => {
const newScene = new Scene({
id: 'map',
map: new GaodeMapV2({
style: 'normal',
center: [120, 30],
pitch: 0,
zoom: 6.45,
}),
// logoVisible: false,
});
newScene.on('loaded', () => {
const newControl = new MouseLocation({});
newScene.addControl(newControl);
// const zoom = new Zoom({
// position: 'topright',
// });
// newScene.addControl(zoom);
});
}, []);
return (
<>
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
</>
);
};
export default Demo;

View File

@ -0,0 +1,6 @@
---
title: 定位
order: 10
---
<code src="./navigation.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,49 @@
import { GaodeMapV2, GeoLocate, Scene } from '@antv/l7';
import gcoord from 'gcoord';
import React, { useState } from 'react';
// tslint:disable-next-line:no-duplicate-imports
import { FunctionComponent, useEffect } from 'react';
const Demo: FunctionComponent = () => {
const [scene, setScene] = useState<Scene | undefined>();
useEffect(() => {
const newScene = new Scene({
id: 'map',
map: new GaodeMapV2({
style: 'normal',
center: [120, 30],
pitch: 0,
zoom: 6.45,
preserveDrawingBuffer: true,
// WebGLParams: {
// preserveDrawingBuffer: true,
// },
}),
// logoVisible: false,
});
newScene.on('loaded', () => {
const newControl = new GeoLocate({
transform: (position) => {
return gcoord.transform(position, gcoord.WGS84, gcoord.GCJ02);
},
});
newScene.addControl(newControl);
});
}, []);
return (
<>
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
</>
);
};
export default Demo;

View File

@ -0,0 +1,6 @@
---
title: 控件位置
order: 1
---
<code src="./position.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,64 @@
import { GaodeMap, Logo, PositionName, Scale, Scene, Zoom } from '@antv/l7';
import { FunctionComponent, useEffect } from 'react';
const Demo: FunctionComponent = () => {
useEffect(() => {
const scene = new Scene({
id: 'map',
map: new GaodeMap({
style: 'dark',
center: [120, 30],
pitch: 0,
zoom: 6.45,
}),
// logoVisible: false,
});
scene.on('loaded', () => {
function createTestControl(position: PositionName) {
scene.addControl(
new Zoom({
position,
}),
);
scene.addControl(
new Scale({
position,
}),
);
scene.addControl(
new Logo({
position,
}),
);
}
createTestControl('topleft');
createTestControl('topright');
createTestControl('bottomleft');
createTestControl('bottomright');
createTestControl('lefttop');
createTestControl('leftbottom');
createTestControl('righttop');
createTestControl('rightbottom');
createTestControl('topcenter');
createTestControl('leftcenter');
createTestControl('rightcenter');
createTestControl('bottomcenter');
});
}, []);
return (
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
);
};
export default Demo;

View File

@ -0,0 +1,6 @@
---
title: 插入/移除
order: 4
---
<code src="./remove.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,65 @@
import { GaodeMap, PositionType, Scene, Zoom } from '@antv/l7';
import React, { useState } from 'react';
// tslint:disable-next-line:no-duplicate-imports
import { FunctionComponent, useEffect } from 'react';
const POSITION_LIST = Object.values(PositionType);
const Demo: FunctionComponent = () => {
const [scene, setScene] = useState<Scene | null>(null);
const [zoom, setZoom] = useState<Zoom | null>(null);
useEffect(() => {
const newScene = new Scene({
id: 'map',
map: new GaodeMap({
style: 'dark',
center: [120, 30],
pitch: 0,
zoom: 6.45,
}),
// logoVisible: false,
});
newScene.on('loaded', () => {
setScene(newScene);
});
}, []);
return (
<>
<button
disabled={!!zoom}
onClick={() => {
if (!zoom) {
const newZoom = new Zoom();
scene?.addControl(newZoom);
setZoom(newZoom);
}
}}
>
</button>
<button
disabled={!zoom}
onClick={() => {
if (zoom) {
scene?.removeControl(zoom);
setZoom(null);
}
}}
>
</button>
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
</>
);
};
export default Demo;

View File

@ -0,0 +1,6 @@
---
title: 比例尺
order: 14
---
<code src="./scale.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,63 @@
import { GaodeMapV2, Scene, Scale } from '@antv/l7';
import React, { useState } from 'react';
// tslint:disable-next-line:no-duplicate-imports
import { FunctionComponent, useEffect } from 'react';
const Demo: FunctionComponent = () => {
const [control, setControl] = useState<Scale | null>(null);
useEffect(() => {
const newScene = new Scene({
id: 'map',
// map: new GaodeMap({
// center: [120, 30],
// pitch: 0,
// zoom: 6.45,
// }),
map: new GaodeMapV2({
center: [120, 30],
pitch: 0,
zoom: 6.45,
style: 'normal',
}),
});
newScene.on('loaded', () => {
const scale = new Scale({
metric: true,
position: 'rightbottom',
// imperial: true,
});
// const zoom = new Zoom({
// position: 'rightbottom',
// });
newScene.addControl(scale);
// newScene.addControl(zoom);
setControl(scale);
});
}, []);
return (
<>
<button
onClick={() => {
control?.setOptions({
imperial: true,
metric: false,
});
}}
>
</button>
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
</>
);
};
export default Demo;

View File

@ -0,0 +1,6 @@
---
title: 更新配置
order: 2
---
<code src="./setOptions.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,54 @@
import { GaodeMap, PositionType, Scene, Zoom } from '@antv/l7';
import React, { useState } from 'react';
// tslint:disable-next-line:no-duplicate-imports
import { FunctionComponent, useEffect } from 'react';
const POSITION_LIST = Object.values(PositionType);
const Demo: FunctionComponent = () => {
const [zoom, setZoom] = useState(() => {
return new Zoom({
position: 'topleft',
});
});
useEffect(() => {
const scene = new Scene({
id: 'map',
map: new GaodeMap({
style: 'dark',
center: [120, 30],
pitch: 0,
zoom: 6.45,
}),
// logoVisible: false,
});
scene.on('loaded', () => {
scene.addControl(zoom);
});
}, []);
const onChangePosition = () => {
const randomIndex = Math.floor(Math.random() * POSITION_LIST.length);
zoom.setOptions({
position: POSITION_LIST[randomIndex],
className: `random-class-${Math.floor(Math.random() * 100)}`,
});
};
return (
<>
<button onClick={onChangePosition}>Options</button>
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
</>
);
};
export default Demo;

View File

@ -0,0 +1,6 @@
---
title: 缩放
order: 13
---
<code src="./zoom.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,77 @@
import { GaodeMap, Scene, Zoom } from '@antv/l7';
import React, { useState } from 'react';
// tslint:disable-next-line:no-duplicate-imports
import { FunctionComponent, useEffect } from 'react';
const Demo: FunctionComponent = () => {
const [control, setControl] = useState<Zoom | null>(null);
useEffect(() => {
const newScene = new Scene({
id: 'map',
map: new GaodeMap({
center: [120, 30],
pitch: 0,
zoom: 6.45,
}),
});
newScene.on('loaded', () => {
const newControl = new Zoom();
newScene.addControl(newControl);
setControl(newControl);
});
}, []);
return (
<>
<button
onClick={() => {
control?.setOptions({
zoomInText: '加',
zoomInTitle: 'in',
zoomOutText: '减',
zoomOutTitle: 'out',
});
}}
>
</button>
<button
onClick={() => {
control?.setOptions({
zoomInText: undefined,
zoomInTitle: undefined,
zoomOutText: undefined,
zoomOutTitle: undefined,
});
}}
>
</button>
<button
onClick={() => {
control?.enable();
}}
>
</button>
<button
onClick={() => {
control?.disable();
}}
>
</button>
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
</>
);
};
export default Demo;

View File

@ -0,0 +1,6 @@
---
title: 图层气泡
order: 2
---
<code src="./layerPopup.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,118 @@
import {
GaodeMap,
LayerPopup,
PointLayer,
Scene,
LineLayer,
// anchorType,
} from '@antv/l7';
import { featureCollection, point } from '@turf/turf';
import React, { useState } from 'react';
// tslint:disable-next-line:no-duplicate-imports
import { FunctionComponent, useEffect } from 'react';
const Demo: FunctionComponent = () => {
const [scene, setScene] = useState<Scene | null>(null);
const [popup, setPopup] = useState<LayerPopup | null>(null);
useEffect(() => {
const newScene = new Scene({
id: 'map',
map: new GaodeMap({
style: 'dark',
center: [120.104697, 30.260704],
pitch: 0,
zoom: 15,
}),
// logoVisible: false,
});
newScene.on('loaded', () => {
const pointLayer = new PointLayer({
name: 'pointLayer',
});
pointLayer
.source(
featureCollection([
point([120.104697, 30.260704], {
name: '测试点1',
lng: 120.104697,
lat: 30.260704,
}),
point([120.104697, 30.261715], {
name: '测试点2',
lng: 120.104697,
lat: 30.261715,
}),
]),
)
.color('#ff0000')
.size(10);
const lineString = new LineLayer({
name: 'lineLayer',
});
lineString
.source(
featureCollection([
{
type: 'Feature',
properties: {
name: '测试线3',
},
geometry: {
type: 'LineString',
coordinates: [
[120.103615, 30.262026],
[120.103172, 30.261771],
[120.102697, 30.261934],
],
},
},
]),
)
.size(6)
.color('#00ff00');
newScene.addLayer(pointLayer);
newScene.addLayer(lineString);
const newPopup = new LayerPopup({
config: [
{
layer: 'pointLayer',
fields: [
{
field: 'name',
formatField: (key) => {
return '名称';
},
},
'lng',
'lat',
],
},
{
layer: 'lineLayer',
fields: ['name'],
},
],
trigger: 'hover',
});
newScene.addPopup(newPopup);
setPopup(newPopup);
setScene(newScene);
});
}, []);
return (
<>
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
</>
);
};
export default Demo;

View File

@ -0,0 +1,6 @@
---
title: 气泡
order: 1
---
<code src="./popup.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,256 @@
import {
GaodeMap,
PointLayer,
Popup,
Scene,
Fullscreen,
anchorType,
// anchorType,
} from '@antv/l7';
import { featureCollection, point } from '@turf/turf';
import React, { useState } from 'react';
// tslint:disable-next-line:no-duplicate-imports
import { FunctionComponent, useEffect } from 'react';
const Demo: FunctionComponent = () => {
const [scene, setScene] = useState<Scene | null>(null);
const [popup, setPopup] = useState<Popup | null>(null);
useEffect(() => {
const newScene = new Scene({
id: 'map',
map: new GaodeMap({
style: 'dark',
center: [120.104697, 30.260704],
pitch: 0,
zoom: 15,
}),
// logoVisible: false,
});
newScene.on('loaded', () => {
const newPopup = new Popup({
closeOnClick: true,
closeOnEsc: true,
lngLat: {
lng: 120.104697,
lat: 30.260704,
},
anchor: 'bottom-right',
title: 'Popup Title',
html: 'Popup Content',
});
newScene.addPopup(newPopup);
const pointLayer = new PointLayer();
pointLayer
.source(featureCollection([point([120.104697, 30.260704])]))
.color('#ff0000')
.size(10);
newScene.addLayer(pointLayer);
setPopup(newPopup);
const fullscreen = new Fullscreen();
newScene.addControl(fullscreen);
setScene(newScene);
});
}, []);
return (
<>
<div>
<button
onClick={() => {
popup?.show();
}}
>
show
</button>
<button
onClick={() => {
popup?.hide();
}}
>
hide
</button>
<button
onClick={() => {
popup?.setOptions({
closeButton: false,
});
}}
>
closeButton
</button>
<button
onClick={() => {
popup?.setOptions({
closeButtonOffsets: [10, 10],
});
}}
>
closeButtonOffsets
</button>
<button
onClick={() => {
popup?.setOptions({
closeOnClick: false,
});
}}
>
closeOnClick
</button>
<button
onClick={() => {
popup?.setOptions({
closeOnEsc: false,
});
}}
>
closeOnEsc
</button>
<button
onClick={() => {
popup?.setOptions({
maxWidth: '50px',
});
}}
>
maxWidth
</button>
<button
onClick={() => {
popup?.setOptions({
anchor: anchorType['BOTTOM-LEFT'],
});
}}
>
anchor
</button>
<button
onClick={() => {
popup?.setOptions({
offsets: [10, 10],
});
}}
>
offsets
</button>
<button
onClick={() => {
popup?.setOptions({
autoPan: true,
});
popup?.setLnglat({
lng: 120,
lat: 30,
});
}}
>
autoPan
</button>
<button
onClick={() => {
popup?.setOptions({
autoClose: false,
});
}}
>
autoClose
</button>
<button
onClick={() => {
popup?.setOptions({
followCursor: true,
});
}}
>
followCursor
</button>
<button
onClick={() => {
popup?.setOptions({
className: 'text-class',
});
}}
>
className
</button>
<button
onClick={() => {
popup?.setOptions({
style: 'background-color: #ff0000',
});
}}
>
style
</button>
<button
onClick={() => {
popup?.setOptions({
text: 'text',
});
}}
>
text
</button>
<button
onClick={() => {
popup?.setOptions({
html: 'html',
});
}}
>
html
</button>
<button
onClick={() => {
popup?.setOptions({
lngLat: {
lng: 120.103797,
lat: 30.260804,
},
});
}}
>
lngLat
</button>
<button
onClick={() => {
popup?.setOptions({
title: undefined,
});
}}
>
title
</button>
<button
onClick={() => {
const newPopup = new Popup({
// autoPan: true,
html: 'fjdksl',
lngLat: {
lng: 120.103797,
lat: 30.260804,
},
});
scene?.addPopup(newPopup);
}}
>
addPopup
</button>
</div>
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
</>
);
};
export default Demo;

View File

@ -0,0 +1,6 @@
---
title: Scene
order: 3
---
<code src="./boxSelect.tsx" compact defaultShowCode></code>

View File

@ -0,0 +1,70 @@
import { GaodeMap, Scene } from '@antv/l7';
import React, { useState } from 'react';
// tslint:disable-next-line:no-duplicate-imports
import { FunctionComponent, useEffect } from 'react';
const Demo: FunctionComponent = () => {
const [scene, setScene] = useState<Scene | null>(null);
useEffect(() => {
const newScene = new Scene({
id: 'map',
map: new GaodeMap({
style: 'dark',
center: [120.104697, 30.260704],
pitch: 0,
zoom: 15,
}),
// logoVisible: false,
});
newScene.on('loaded', () => {
setScene(newScene);
newScene.on('selectstart', (...params) => {
// tslint:disable-next-line:no-console
console.log('selectstart', ...params);
});
newScene.on('selecting', (...params) => {
// tslint:disable-next-line:no-console
console.log('selecting', ...params);
});
newScene.on('selectend', (...params) => {
// tslint:disable-next-line:no-console
console.log('selectend', ...params);
});
});
}, []);
return (
<>
<div>
<button
onClick={() => {
scene?.enableBoxSelect(true);
}}
>
</button>
<button
onClick={() => {
scene?.disableBoxSelect();
}}
>
</button>
</div>
<div
id="map"
style={{
height: '500px',
position: 'relative',
}}
/>
</>
);
};
export default Demo;

View File

@ -29,23 +29,31 @@ module.exports = {
babelConfig: require('./babel.config.js'),
},
},
moduleFileExtensions: [ 'ts', 'tsx', 'js' ],
modulePathIgnorePatterns: [ 'dist' ],
moduleFileExtensions: ['ts', 'tsx', 'js'],
modulePathIgnorePatterns: ['dist'],
moduleNameMapper: {
'@antv/l7-(.+)$': '<rootDir>packages/$1/src'
'@antv/l7-(.+)$': '<rootDir>packages/$1/src',
},
notify: true,
notifyMode: 'always',
roots: [ '<rootDir>packages' ],
testMatch: [ '**/__tests__/*.spec.+(ts|tsx|js)', '**/*.test.+(ts|tsx|js)', '**/__tests__/*/*.spec.+(ts|tsx|js)' ],
roots: ['<rootDir>packages'],
testMatch: [
'**/__tests__/*.spec.+(ts|tsx|js)',
'**/*.test.+(ts|tsx|js)',
'**/__tests__/*/*.spec.+(ts|tsx|js)',
],
transform: {
// '^.+\\.(ts|tsx)$': 'ts-jest',
// @see https://github.com/kulshekhar/ts-jest/issues/1130
'^.+\\.(ts|tsx)$': 'babel-jest'
'^.+\\.(ts|tsx)$': 'babel-jest',
screenfull: 'babel-jest',
'\\.(less|css)$': 'jest-less-loader',
'\\.png$': 'jest-file-loader',
},
setupFilesAfterEnv: [ '<rootDir>jest/setupTests.ts' ],
snapshotSerializers: [ 'enzyme-to-json/serializer' ],
coverageReporters: ['html', 'lcov', 'clover'],
coveragePathIgnorePatterns: ['/node_modules/', '/iconfont/'],
coverageThreshold: {
global: {
branches: 9,

View File

@ -101,10 +101,13 @@
"husky": "^3.0.9",
"jest": "^24.9.0",
"jest-canvas-mock": "^2.4.0",
"jest-file-loader": "^1.0.2",
"jest-less-loader": "^0.1.2",
"jest-styled-components": "^6.2.1",
"leaflet": "^1.8.0",
"lerc": "^3.0.0",
"lerna": "^3.16.4",
"less": "^4.1.3",
"lint-staged": "^9.2.4",
"mockjs": "^1.1.0",
"npm-run-all": "^4.1.5",
@ -153,7 +156,8 @@
"webpack-dev-server": "^3.1.7",
"webpack-merge": "^4.1.4",
"worker-loader": "^2.0.0",
"yorkie": "^2.0.0"
"yorkie": "^2.0.0",
"gcoord": "^0.3.2"
},
"scripts": {
"dev": "npm run worker && dumi dev",
@ -221,6 +225,5 @@
},
"tnpm": {
"mode": "yarn"
},
"dependencies": {}
}
}

View File

@ -0,0 +1,54 @@
import { TestScene } from '@antv/l7-test-utils';
import ButtonControl from '../src/control/baseControl/buttonControl';
import { createL7Icon } from '../src/utils/icon';
class TestControl extends ButtonControl {}
describe('buttonControl', () => {
const scene = TestScene();
it('life cycle', () => {
const control = new TestControl();
scene.addControl(control);
const container = control.getContainer();
expect(container.parentElement).toBeInstanceOf(HTMLElement);
scene.removeControl(control);
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
});
it('disable', () => {
const control = new TestControl();
scene.addControl(control);
control.setIsDisable(true);
expect(control.getContainer().getAttribute('disabled')).not.toBeNull();
control.setIsDisable(false);
expect(control.getContainer().getAttribute('disabled')).toBeNull();
});
it('options', () => {
const control = new TestControl({
title: '导出图片',
btnText: '导出图片',
btnIcon: createL7Icon('l7-icon-tupian'),
});
scene.addControl(control);
const container = control.getContainer();
expect(container.classList).toContain('l7-button-control');
expect(container.getAttribute('title')).toContain('导出图片');
const textContainer = container.querySelector('.l7-button-control__text')!;
expect(textContainer).toBeInstanceOf(HTMLElement);
control.setOptions({
title: undefined,
btnText: '替换文本',
btnIcon: createL7Icon('l7-icon-tupian1'),
});
expect(container.getAttribute('title')).toBeFalsy();
});
});

View File

@ -0,0 +1,68 @@
import { TestScene } from '@antv/l7-test-utils';
import { DOM } from '@antv/l7-utils';
import { Control } from '../src/control/baseControl';
class TestControl extends Control {
public onAdd(): HTMLElement {
return DOM.create('div');
}
public onRemove(): void {}
}
describe('control', () => {
const scene = TestScene();
it('life cycle', () => {
const className1 = 'testControl1';
const className2 = 'testControl2';
const control1 = new TestControl({
className: className1,
});
const control2 = new TestControl({
className: className2,
});
scene.addControl(control1);
scene.addControl(control2);
const dom1 = document.querySelector(`.${className1}`);
expect(dom1).toBeInstanceOf(HTMLElement);
const dom2 = document.querySelector(`.${className2}`);
expect(dom2).toBeInstanceOf(HTMLElement);
scene.removeControl(control1);
scene.removeControl(control2);
const dom3 = document.querySelector(`.${className1}`);
expect(dom3).toBeNull();
const dom4 = document.querySelector(`.${className2}`);
expect(dom4).toBeNull();
});
it('show hide', () => {
const control = new TestControl();
scene.addControl(control);
control.hide();
expect(control.getContainer().classList).toContain('l7-control--hide');
expect(control.getIsShow()).toEqual(false);
control.show();
expect(control.getContainer().classList).not.toContain('l7-control--hide');
expect(control.getIsShow()).toEqual(true);
});
it('options', () => {
const className = 'gunala';
const color = 'rgb(255, 0, 0)';
const control = new TestControl({});
scene.addControl(control);
control.setOptions({
position: 'leftbottom',
className,
style: `color: ${color};`,
});
const container = control.getContainer();
const corner = container.parentElement!;
expect(corner.classList).toContain('l7-left');
expect(corner.classList).toContain('l7-bottom');
expect(container.classList).toContain(className);
expect(container.style.color).toEqual(color);
});
});

View File

@ -0,0 +1,32 @@
import { TestScene } from '@antv/l7-test-utils';
import ExportImage from '../src/control/exportImage';
describe('exportImage', () => {
const scene = TestScene();
it('life cycle', () => {
const control = new ExportImage({});
scene.addControl(control);
const container = control.getContainer();
expect(container.parentElement).toBeInstanceOf(HTMLElement);
scene.removeControl(control);
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
});
it('image', () => {
const control = new ExportImage({
onExport: (base64) => {
// tslint:disable-next-line:no-console
// console.log(base64);
},
});
scene.addControl(control);
const button = control.getContainer() as HTMLDivElement;
button.click();
expect(button.parentElement).toBeInstanceOf(HTMLElement);
});
});

View File

@ -0,0 +1,27 @@
import { TestScene } from '@antv/l7-test-utils';
import Fullscreen from '../src/control/fullscreen';
describe('fullscreen', () => {
const scene = TestScene();
it('life cycle', () => {
const control = new Fullscreen({});
scene.addControl(control);
const container = control.getContainer();
expect(container.parentElement).toBeInstanceOf(HTMLElement);
scene.removeControl(control);
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
});
it('fullscreen', () => {
const control = new Fullscreen({});
scene.addControl(control);
const button = control.getContainer() as HTMLDivElement;
button.click();
expect(button.parentElement).toBeInstanceOf(HTMLElement);
});
});

View File

@ -0,0 +1,19 @@
import { TestScene } from '@antv/l7-test-utils';
import LayerControl from '../src/control/layerControl';
describe('layerControl', () => {
const scene = TestScene();
it('life cycle', () => {
const layerControl = new LayerControl();
scene.addControl(layerControl);
const container = layerControl.getContainer();
expect(container.parentElement).toBeInstanceOf(HTMLElement);
expect(layerControl.getLayerVisible()).toEqual([]);
scene.removeControl(layerControl);
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
});
});

View File

@ -0,0 +1,43 @@
import { PointLayer } from '@antv/l7-layers';
import { TestScene } from '@antv/l7-test-utils';
import LayerPopup from '../src/popup/layerPopup';
describe('popup', () => {
const scene = TestScene();
const testClassName = 'l7-layer-popup-test';
it('life cycle', () => {
const pointLayer = new PointLayer();
pointLayer.source([{ lng: 120, lat: 30 }], {
parser: {
type: 'json',
x: 'lng',
y: 'lat',
},
});
const layerPopup = new LayerPopup({
className: testClassName,
config: [
{
layer: pointLayer,
fields: [
{
field: 'lng',
},
],
},
],
});
scene.addPopup(layerPopup);
expect(layerPopup.isOpen()).toEqual(true);
layerPopup.setOptions({
trigger: 'click',
});
scene.removePopup(layerPopup);
expect(layerPopup.isOpen()).toEqual(false);
});
});

View File

@ -0,0 +1,41 @@
import { TestScene } from '@antv/l7-test-utils';
import MapTheme from '../src/control/mapTheme';
describe('mapTheme', () => {
const scene = TestScene();
it('life cycle', () => {
const control = new MapTheme({});
scene.addControl(control);
const container = control.getContainer();
expect(container.parentElement).toBeInstanceOf(HTMLElement);
scene.removeControl(control);
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
});
it('mapTheme', () => {
const control = new MapTheme({
defaultValue: 'normal',
});
scene.addControl(control);
const options = control.getOptions().options;
expect(options.length).toBeGreaterThan(0);
expect(control.getSelectValue()).toEqual(
'mapbox://styles/mapbox/streets-v11',
);
const optionList = ((control
.getPopper()
.getContent() as HTMLDivElement).querySelectorAll(
'.l7-select-control-item',
) as unknown) as HTMLDivElement[];
optionList[1].click();
// expect(control.getSelectValue()).toEqual(
// 'mapbox://styles/zcxduo/ck2ypyb1r3q9o1co1766dex29',
// );
});
});

View File

@ -1,15 +1,14 @@
import { TestScene } from '@antv/l7-test-utils';
import Marker from '../src/marker';
import Popup from '../src/popup';
import { TestScene } from '@antv/l7-test-utils'
import Popup from '../src/popup/popup';
const popup = new Popup({ offsets: [0, 20] })
.setHTML('<h1 onclick= alert("123")>111</h1>');
const popup = new Popup({ offsets: [0, 20] }).setHTML(
'<h1 onclick= alert("123")>111</h1>',
);
const marker = new Marker()
.setLnglat({ lng: 120, lat: 30 })
.setPopup(popup);
const marker = new Marker().setLnglat({ lng: 120, lat: 30 }).setPopup(popup);
TestScene().addMarker(marker)
TestScene().addMarker(marker);
describe('Marker', () => {
it('render and remove correctly', () => {
@ -18,7 +17,7 @@ describe('Marker', () => {
expect(marker.getDefault().color).toEqual('#5B8FF9');
expect(marker.getOffset()).toEqual([0, 0]);
expect(marker.isDraggable()).toEqual(false);
marker.remove()
marker.remove();
expect(document.querySelector('.l7-marker')).toBeFalsy();
});
@ -34,13 +33,13 @@ describe('Marker', () => {
marker.closePopup();
expect(marker.getPopup().isOpen()).toBeFalsy();
})
});
it('longitude and latitude', () => {
const { lng, lat } = marker.getLnglat();
expect(lng).toEqual(120);
expect(lat).toEqual(30);
marker.setLnglat({ lng: 121, lat: 31 })
marker.setLnglat({ lng: 121, lat: 31 });
const { lng: newLng, lat: newLat } = marker.getLnglat();
expect(newLng).toEqual(121);
expect(newLat).toEqual(31);
@ -55,7 +54,7 @@ describe('Marker', () => {
});
it('extData', () => {
marker.setExtData({ test: 1 })
expect(marker.getExtData()).toEqual({ test: 1 })
marker.setExtData({ test: 1 });
expect(marker.getExtData()).toEqual({ test: 1 });
});
});

View File

@ -0,0 +1,31 @@
import { TestScene } from '@antv/l7-test-utils';
import MouseLocation from '../src/control/mouseLocation';
describe('buttonControl', () => {
const scene = TestScene();
it('life cycle', () => {
const control = new MouseLocation({});
scene.addControl(control);
const container = control.getContainer();
expect(container.parentElement).toBeInstanceOf(HTMLElement);
scene.removeControl(control);
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
});
it('life cycle', () => {
const control = new MouseLocation();
scene.addControl(control);
(scene.getMapService().map as any).emit('mousemove', {
lngLat: {
lng: 120,
lat: 30,
},
});
expect(control.getLocation()).toEqual([120, 30]);
});
});

View File

@ -0,0 +1,16 @@
import { TestScene } from '@antv/l7-test-utils';
import Navigation from '../src/control/geoLocate';
describe('navigation', () => {
const scene = TestScene();
it('navigation', () => {
const control = new Navigation({});
scene.addControl(control);
const button = control.getContainer() as HTMLDivElement;
button.click();
expect(button.parentElement).toBeInstanceOf(HTMLElement);
});
});

View File

@ -0,0 +1,38 @@
import { TestScene } from '@antv/l7-test-utils';
import PopperControl from '../src/control/baseControl/popperControl';
class TestControl extends PopperControl {}
describe('popperControl', () => {
const scene = TestScene();
it('life cycle', () => {
const control = new TestControl({});
scene.addControl(control);
const container = control.getContainer();
expect(container.parentElement).toBeInstanceOf(HTMLElement);
scene.removeControl(control);
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
});
it('popper', () => {
const control = new TestControl({
popperTrigger: 'click',
});
scene.addControl(control);
});
it('options', () => {
const control = new TestControl({});
scene.addControl(control);
const testClassName = 'testPopper';
control.setOptions({
popperClassName: testClassName,
});
expect(control.getPopper().getPopperDOM().classList).toContain(
testClassName,
);
});
});

View File

@ -0,0 +1,43 @@
import { TestScene } from '@antv/l7-test-utils';
import Popup from '../src/popup/popup';
describe('popup', () => {
const scene = TestScene();
const className = 'text-class-popup';
it('life cycle', () => {
const popup = new Popup({
html: '123456',
className: className,
lngLat: {
lng: 120,
lat: 30,
},
});
popup.setOptions({
lngLat: { lng: 130, lat: 40 },
});
scene.addPopup(popup);
const targetPopup = document.querySelector(`.${className}`) as HTMLElement;
expect(targetPopup).not.toBeFalsy();
expect(popup.getLnglat()).toEqual({
lng: 130,
lat: 40,
});
expect(/123456/.test(targetPopup.innerHTML)).toEqual(true);
expect(targetPopup.classList.contains('l7-popup-hide')).toEqual(false);
popup.hide();
expect(targetPopup.classList.contains('l7-popup-hide')).toEqual(true);
popup.show();
expect(targetPopup.classList.contains('l7-popup-hide')).toEqual(false);
});
});

View File

@ -0,0 +1,38 @@
import { TestScene } from '@antv/l7-test-utils';
import Scale from '../src/control/scale';
describe('scale', () => {
const scene = TestScene();
it('life cycle', () => {
const scale = new Scale();
scene.addControl(scale);
const container = scale.getContainer();
expect(container.parentElement).toBeInstanceOf(HTMLElement);
expect(
/\d+\s?km/i.test(
container
.querySelector('.l7-control-scale-line')
?.innerHTML.toLowerCase() ?? '',
),
).toEqual(true);
scale.setOptions({
metric: false,
imperial: true,
});
expect(
/\d+\s?mi/i.test(
container
.querySelector('.l7-control-scale-line')
?.innerHTML.toLowerCase() ?? '',
),
).toEqual(true);
scene.removeControl(scale);
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
});
});

View File

@ -0,0 +1,105 @@
import { TestScene } from '@antv/l7-test-utils';
import SelectControl from '../src/control/baseControl/selectControl';
import { createL7Icon } from '../src/utils/icon';
class SingleControl extends SelectControl {
public getDefault(option: any): any {
return {
...super.getDefault(option),
options: [
{
icon: createL7Icon('icon-1'),
label: '1',
value: '1',
},
{
icon: createL7Icon('icon-2'),
label: '2',
value: '2',
},
],
defaultValue: '2',
};
}
protected getIsMultiple(): boolean {
return false;
}
}
class MultiControl extends SelectControl {
public getDefault(option: any): any {
return {
...super.getDefault(option),
options: [
{
img: '1',
label: '1',
value: '1',
},
{
img: '1',
label: '2',
value: '2',
},
],
defaultValue: ['2'],
};
}
protected getIsMultiple(): boolean {
return true;
}
}
describe('selectControl', () => {
const scene = TestScene();
it('life cycle', () => {
const control = new SingleControl({});
scene.addControl(control);
const container = control.getContainer();
expect(container.parentElement).toBeInstanceOf(HTMLElement);
scene.removeControl(control);
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
});
it('normal single select', () => {
const control = new SingleControl({});
scene.addControl(control);
expect(control.getSelectValue()).toEqual('2');
const popperContainer = control.getPopper().getContent() as HTMLDivElement;
const optionDomList = Array.from(
popperContainer.querySelectorAll('.l7-select-control-item'),
) as HTMLDivElement[];
expect(optionDomList).toHaveLength(2);
expect(optionDomList[0].getAttribute('data-option-value')).toEqual('1');
expect(optionDomList[0].getAttribute('data-option-index')).toEqual('0');
optionDomList[0].click();
expect(control.getSelectValue()).toEqual('1');
});
it('img multiple select', () => {
const control = new MultiControl({});
scene.addControl(control);
expect(control.getSelectValue()).toEqual(['2']);
const popperContainer = control.getPopper().getContent() as HTMLDivElement;
const optionDomList = Array.from(
popperContainer.querySelectorAll('.l7-select-control-item'),
) as HTMLDivElement[];
expect(optionDomList).toHaveLength(2);
expect(optionDomList[0].getAttribute('data-option-value')).toEqual('1');
expect(optionDomList[0].getAttribute('data-option-index')).toEqual('0');
expect(popperContainer.querySelectorAll('img')).toHaveLength(2);
optionDomList[0].click();
expect(control.getSelectValue()).toEqual(['2', '1']);
optionDomList[0].click();
optionDomList[1].click();
expect(control.getSelectValue()).toEqual([]);
});
});

View File

@ -0,0 +1,96 @@
import { DOM } from '@antv/l7-utils';
import { createL7Icon } from '../src/utils/icon';
import { Popper } from '../src/utils/popper';
describe('util', () => {
it('icon', () => {
const testClassName = 'l7-test-icon';
const testIcon = createL7Icon(testClassName);
expect(testIcon).toBeInstanceOf(SVGElement);
expect(testIcon.tagName.toLowerCase()).toEqual('svg');
const classList = testIcon.classList;
expect(classList).toContain('l7-iconfont');
});
it('popper', () => {
const button = DOM.create('button') as HTMLButtonElement;
button.innerText = 'Test';
document.body.append(button);
const testContent = '123456';
const popper1 = new Popper(button, {
placement: 'left-start',
trigger: 'click',
content: testContent,
className: 'test-popper-class',
container: document.body,
unique: true,
});
const getPopperClassList = (popper: Popper) => {
return popper.popperDOM.classList;
};
popper1.show();
expect(popper1.getContent()).toEqual(testContent);
expect(getPopperClassList(popper1)).toContain('l7-popper');
expect(getPopperClassList(popper1)).toContain('test-popper-class');
expect(getPopperClassList(popper1)).toContain('l7-popper-left');
expect(getPopperClassList(popper1)).toContain('l7-popper-start');
expect(getPopperClassList(popper1)).not.toContain('l7-popper-hide');
popper1.hide();
button.click();
expect(getPopperClassList(popper1)).not.toContain('l7-popper-hide');
button.click();
expect(getPopperClassList(popper1)).toContain('l7-popper-hide');
const newTestContent = DOM.create('div') as HTMLDivElement;
newTestContent.innerText = '789456';
popper1.setContent(newTestContent);
expect(popper1.contentDOM.firstChild).toEqual(newTestContent);
popper1.show();
const popper2 = new Popper(button, {
placement: 'right-end',
container: document.body,
trigger: 'click',
content: 'hover',
}).show();
expect(getPopperClassList(popper2)).toContain('l7-popper-end');
expect(getPopperClassList(popper2)).toContain('l7-popper-right');
const popper3 = new Popper(button, {
placement: 'top-start',
container: document.body,
trigger: 'click',
content: 'hover',
}).show();
expect(getPopperClassList(popper3)).toContain('l7-popper-top');
expect(getPopperClassList(popper3)).toContain('l7-popper-start');
const popper4 = new Popper(button, {
placement: 'bottom-end',
container: document.body,
trigger: 'click',
content: 'hover',
}).show();
expect(getPopperClassList(popper4)).toContain('l7-popper-bottom');
expect(getPopperClassList(popper4)).toContain('l7-popper-end');
const popper5 = new Popper(button, {
placement: 'left',
container: document.body,
trigger: 'click',
content: 'hover',
}).show();
expect(getPopperClassList(popper5)).toContain('l7-popper-left');
const popper6 = new Popper(button, {
placement: 'top',
container: document.body,
trigger: 'click',
content: 'hover',
}).show();
expect(getPopperClassList(popper6)).toContain('l7-popper-top');
});
});

View File

@ -0,0 +1,34 @@
import { TestScene } from '@antv/l7-test-utils';
import Zoom from '../src/control/zoom';
describe('zoom', () => {
const scene = TestScene();
it('life cycle', () => {
const zoom = new Zoom();
scene.addControl(zoom);
const container = zoom.getContainer();
expect(container.parentElement).toBeInstanceOf(HTMLElement);
scene.removeControl(zoom);
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
});
it('zoom getDefault', () => {
const zoom = new Zoom();
scene.addControl(zoom);
zoom.disable();
const btnList = Array.from(zoom.getContainer().querySelectorAll('button'));
expect(btnList.map((item) => item.getAttribute('disabled'))).toEqual([
'true',
'true',
]);
zoom.enable();
expect(btnList.map((item) => item.getAttribute('disabled'))).toEqual([
null,
null,
]);
});
});

View File

@ -13,6 +13,7 @@
],
"scripts": {
"tsc": "tsc --project tsconfig.build.json",
"less": "lessc src/css/index.less src/css/index.css",
"clean": "rimraf dist; rimraf es; rimraf lib;",
"build": "father build",
"build:cjs": "BABEL_ENV=cjs babel src --root-mode upward --out-dir lib --source-maps --extensions .ts,.tsx --delete-dir-on-start --no-comments",
@ -35,7 +36,9 @@
"supercluster": "^7.0.0"
},
"devDependencies": {
"@antv/l7-test-utils": "2.9.37"
"@antv/l7-test-utils": "2.9.37",
"gcoord": "^0.3.2",
"less": "^4.1.3"
},
"gitHead": "684ba4eb806a798713496d3fc0b4d1e17517dc31",
"publishConfig": {

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,75 @@
export const GaodeMapStyleConfig = {
normal: {
text: '标准',
img:
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*-nqiT6Vu948AAAAAAAAAAAAAARQnAQ',
},
light: {
text: '月光银',
img:
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*J_wYQL_PaUEAAAAAAAAAAAAAARQnAQ',
},
dark: {
text: '幻影黑',
img:
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*U7M9QI1yat4AAAAAAAAAAAAAARQnAQ',
},
fresh: {
text: '草色青',
img:
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*T-oBT4hB5ucAAAAAAAAAAAAAARQnAQ',
},
grey: {
text: '雅士灰',
img:
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*OREXQ4vgQRIAAAAAAAAAAAAAARQnAQ',
},
graffiti: {
text: '涂鸦',
img:
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*4UApTKmeiy4AAAAAAAAAAAAAARQnAQ',
},
macaron: {
text: '马卡龙',
img:
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*0GrCQLtDjNcAAAAAAAAAAAAAARQnAQ',
},
darkblue: {
text: '极夜蓝',
img:
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*uWxqSZQlPkkAAAAAAAAAAAAAARQnAQ',
},
wine: {
text: '酱籽',
img:
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*OFPrTbg3an0AAAAAAAAAAAAAARQnAQ',
},
};
export const MapboxMapStyleConfig = {
normal: {
text: '标准',
img:
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*AnfJTbIBJOkAAAAAAAAAAAAAARQnAQ',
},
light: {
text: '亮',
img:
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*gnuiQIok9qIAAAAAAAAAAAAAARQnAQ',
},
dark: {
text: '暗',
img:
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*NwG-TbOlBH0AAAAAAAAAAAAAARQnAQ',
},
satellite: {
text: '卫星',
img:
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*2X5EQLKul3IAAAAAAAAAAAAAARQnAQ',
},
outdoors: {
text: '户外',
img:
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*gXFLRIaBUI0AAAAAAAAAAAAAARQnAQ',
},
};

View File

@ -1,112 +0,0 @@
import {
IControlOption,
IControlService,
ILayerService,
IMapService,
IRendererService,
PositionType,
TYPES,
} from '@antv/l7-core';
import { DOM } from '@antv/l7-utils';
import { EventEmitter } from 'eventemitter3';
import { Container } from 'inversify';
export { PositionType } from '@antv/l7-core';
let controlId = 0;
export default class Control extends EventEmitter {
public controlOption: IControlOption;
protected container: HTMLElement;
protected sceneContainer: Container;
protected mapsService: IMapService;
protected renderService: IRendererService;
protected layerService: ILayerService;
protected controlService: IControlService;
private isShow: boolean;
constructor(cfg?: Partial<IControlOption>) {
super();
this.controlOption = {
...this.getDefault(),
...(cfg || {}),
};
}
public getDefault() {
return {
position: PositionType.TOPRIGHT,
name: `${controlId++}`,
};
}
public setPosition(position: PositionType = PositionType.BOTTOMRIGHT) {
// 考虑组件的自动布局,需要销毁重建
const controlService = this.controlService;
if (controlService) {
controlService.removeControl(this);
}
this.controlOption.position = position;
if (controlService) {
controlService.addControl(this, this.sceneContainer);
}
return this;
}
public addTo(sceneContainer: Container) {
this.mapsService = sceneContainer.get<IMapService>(TYPES.IMapService);
this.renderService = sceneContainer.get<IRendererService>(
TYPES.IRendererService,
);
this.layerService = sceneContainer.get<ILayerService>(TYPES.ILayerService);
this.controlService = sceneContainer.get<IControlService>(
TYPES.IControlService,
);
this.sceneContainer = sceneContainer;
this.isShow = true;
this.container = this.onAdd();
const container = this.container;
const pos = this.controlOption.position;
const corner = this.controlService.controlCorners[pos];
DOM.addClass(container, 'l7-control');
if (pos.indexOf('bottom') !== -1) {
corner.insertBefore(container, corner.firstChild);
} else {
corner.appendChild(container);
}
return this;
}
public onAdd(): HTMLElement {
throw new Error('Method not implemented.');
}
public onRemove(): void {
throw new Error('Method not implemented.');
}
public hide() {
const container = this.container;
DOM.addClass(container, 'l7-control-hide');
this.isShow = false;
}
public show() {
const container = this.container;
DOM.removeClass(container, 'l7-control-hide');
this.isShow = true;
}
public remove() {
if (!this.mapsService) {
return this;
}
DOM.remove(this.container);
this.onRemove();
}
public _refocusOnMap(e: MouseEvent) {
// if map exists and event is not a keyboard event
if (this.mapsService && e && e.screenX > 0 && e.screenY > 0) {
const container = this.mapsService.getContainer();
if (container !== null) {
container.focus();
}
}
}
}

View File

@ -1,23 +0,0 @@
describe('BaseControl', () => {
// const el = document.createElement('div');
// el.id = 'test-div-id';
// el.style.width = '500px';
// el.style.height = '500px';
// el.style.position = 'absolute';
// document.querySelector('body')?.appendChild(el);
// const scene = new Scene({
// id: 'test-div-id',
// map: new Map({
// style: 'dark',
// center: [110.19382669582967, 30.258134],
// pitch: 0,
// zoom: 3,
// }),
// });
it('control', () => {
expect(1).toEqual(1)
});
});

View File

@ -1,17 +0,0 @@
import Zoom from '../zoom';
import { TestScene } from '@antv/l7-test-utils'
describe('zoom', () => {
const zoom = new Zoom()
it('zoom getDefault', () => {
expect(zoom.getDefault().name).toEqual('zoom');
const scene = TestScene();
scene.addControl(zoom);
zoom.disable();
});
});

View File

@ -0,0 +1,150 @@
import { DOM } from '@antv/l7-utils';
import { ELType } from '@antv/l7-utils/src/dom';
import Control, { IControlOption } from './control';
export { ButtonControl };
export interface IButtonControlOption extends IControlOption {
btnIcon?: ELType | DocumentFragment;
btnText?: string;
title?: string;
vertical?: boolean;
}
export default class ButtonControl<
O extends IButtonControlOption = IButtonControlOption
> extends Control<O> {
/**
*
* @protected
*/
protected isDisable = false;
/**
* DOM
* @protected
*/
protected button?: HTMLElement;
/**
* DOM
* @protected
*/
protected buttonText?: HTMLElement;
/**
* DOM
* @protected
*/
protected buttonIcon?: ELType | DocumentFragment;
/**
*
* @param newIsDisable
*/
public setIsDisable(newIsDisable: boolean) {
this.isDisable = newIsDisable;
if (newIsDisable) {
this.button?.setAttribute('disabled', 'true');
} else {
this.button?.removeAttribute('disabled');
}
}
public createButton(className: string = '') {
return DOM.create(
'button',
`l7-button-control ${className}`,
) as HTMLElement;
}
public onAdd(): HTMLElement {
this.button = this.createButton();
this.isDisable = false;
const { title, btnText, btnIcon } = this.controlOption;
this.setBtnTitle(title);
this.setBtnText(btnText);
this.setBtnIcon(btnIcon);
return this.button;
}
public onRemove(): void {
this.button = this.buttonIcon = this.buttonText = undefined;
this.isDisable = false;
}
/**
*
* @param newOptions
*/
public setOptions(newOptions: Partial<O>) {
const { title, btnText, btnIcon } = newOptions;
if (this.checkUpdateOption(newOptions, ['title'])) {
this.setBtnTitle(title);
}
if (this.checkUpdateOption(newOptions, ['btnIcon'])) {
this.setBtnIcon(btnIcon);
}
if (this.checkUpdateOption(newOptions, ['btnText'])) {
this.setBtnText(btnText);
}
super.setOptions(newOptions);
}
/**
* title
* @param title
*/
public setBtnTitle(title: O['title']) {
this.button?.setAttribute('title', title ?? '');
}
/**
* Icon
* @param newIcon
*/
public setBtnIcon(newIcon: O['btnIcon']) {
if (this.buttonIcon) {
DOM.remove(this.buttonIcon);
}
if (newIcon) {
const firstChild = this.button?.firstChild;
if (firstChild) {
this.button?.insertBefore(newIcon, firstChild);
} else {
this.button?.appendChild(newIcon);
}
this.buttonIcon = newIcon;
}
}
/**
*
* @param newText
*/
public setBtnText(newText: O['btnText']) {
if (!this.button) {
return;
}
DOM.removeClass(this.button, 'l7-button-control--row');
DOM.removeClass(this.button, 'l7-button-control--column');
if (newText) {
let btnText = this.buttonText;
if (!btnText) {
btnText = DOM.create('div', 'l7-button-control__text') as HTMLElement;
this.button?.appendChild(btnText);
this.buttonText = btnText;
}
btnText.innerText = newText;
DOM.addClass(
this.button,
this.controlOption.vertical
? 'l7-button-control--column'
: 'l7-button-control--row',
);
} else if (!newText && this.buttonText) {
DOM.remove(this.buttonText);
this.buttonText = undefined;
}
}
}

View File

@ -0,0 +1,295 @@
import {
IControl,
IControlService,
IGlobalConfigService,
ILayerService,
IMapService,
IRendererService,
ISceneService,
PositionName,
PositionType,
TYPES,
} from '@antv/l7-core';
import { DOM } from '@antv/l7-utils';
import EventEmitter from 'eventemitter3';
import { Container } from 'inversify';
import { ControlEvent } from '../../interface';
export { PositionType } from '@antv/l7-core';
export { Control };
export interface IControlOption {
name: string;
position: PositionName;
className?: string;
style?: string;
[key: string]: any;
}
export default class Control<O extends IControlOption = IControlOption>
extends EventEmitter<ControlEvent>
implements IControl<O> {
/**
*
* @protected
*/
protected static controlCount = 0;
/**
*
*/
public controlOption: O;
/**
* DOM
* @protected
*/
protected container: HTMLElement;
/**
*
* @protected
*/
protected isShow: boolean;
protected sceneContainer: Container;
protected scene: ISceneService;
protected mapsService: IMapService;
protected renderService: IRendererService;
protected layerService: ILayerService;
protected controlService: IControlService;
protected configService: IGlobalConfigService;
constructor(option?: Partial<O>) {
super();
Control.controlCount++;
this.controlOption = {
...this.getDefault(option),
...(option || {}),
};
}
public getOptions() {
return this.controlOption;
}
/**
*
* @param newOptions
*/
public setOptions(newOptions: Partial<O>): void {
const defaultOptions = this.getDefault(newOptions);
(Object.entries(newOptions) as Array<[keyof O, any]>).forEach(
([key, value]) => {
if (value === undefined) {
newOptions[key] = defaultOptions[key];
}
},
);
if ('position' in newOptions) {
this.setPosition(newOptions.position);
}
if ('className' in newOptions) {
this.setClassName(newOptions.className);
}
if ('style' in newOptions) {
this.setStyle(newOptions.style);
}
this.controlOption = {
...this.controlOption,
...newOptions,
};
}
/**
* Control Scene controlService
* @param sceneContainer
*/
public addTo(sceneContainer: Container) {
// 初始化各个 Service 实例
this.mapsService = sceneContainer.get<IMapService>(TYPES.IMapService);
this.renderService = sceneContainer.get<IRendererService>(
TYPES.IRendererService,
);
this.layerService = sceneContainer.get<ILayerService>(TYPES.ILayerService);
this.controlService = sceneContainer.get<IControlService>(
TYPES.IControlService,
);
this.configService = sceneContainer.get<IGlobalConfigService>(
TYPES.IGlobalConfigService,
);
this.scene = sceneContainer.get<ISceneService>(TYPES.ISceneService);
this.sceneContainer = sceneContainer;
this.isShow = true;
// 初始化 container
this.container = this.onAdd();
DOM.addClass(this.container, 'l7-control');
const { className, style } = this.controlOption;
if (className) {
this.setClassName(className);
}
if (style) {
this.setStyle(style);
}
// 将 container 插入容器中
this.insertContainer();
this.emit('add', this);
return this;
}
/**
*
*/
public remove() {
if (!this.mapsService) {
return this;
}
DOM.remove(this.container);
this.onRemove();
this.emit('remove', this);
}
/**
* Control Control DOM
*/
public onAdd(): HTMLElement {
return DOM.create('div');
}
/**
* Control
*/
// tslint:disable-next-line:no-empty
public onRemove() {}
/**
*
*/
public show() {
const container = this.container;
DOM.removeClass(container, 'l7-control--hide');
this.isShow = true;
this.emit('show', this);
}
/**
*
*/
public hide() {
const container = this.container;
DOM.addClass(container, 'l7-control--hide');
this.isShow = false;
this.emit('hide', this);
}
/**
*
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public getDefault(option?: Partial<O>): O {
// tslint:disable-next-line:no-object-literal-type-assertion
return {
position: PositionType.TOPRIGHT,
name: `${Control.controlCount}`,
} as O;
}
/**
* DOM
*/
public getContainer() {
return this.container;
}
/**
* Control
*/
public getIsShow() {
return this.isShow;
}
public _refocusOnMap(e: MouseEvent) {
// if map exists and event is not a keyboard event
if (this.mapsService && e && e.screenX > 0 && e.screenY > 0) {
const container = this.mapsService.getContainer();
if (container !== null) {
container.focus();
}
}
}
/**
*
* @param position
*/
public setPosition(
position: PositionType | PositionName = PositionType.TOPLEFT,
) {
// 考虑组件的自动布局,需要销毁重建
const controlService = this.controlService;
if (controlService) {
controlService.removeControl(this);
}
this.controlOption.position = position;
if (controlService) {
controlService.addControl(this, this.sceneContainer);
}
return this;
}
/**
* container className
* @param className
*/
public setClassName(className?: string | null) {
const container = this.container;
const { className: oldClassName } = this.controlOption;
if (oldClassName) {
DOM.removeClass(container, oldClassName);
}
if (className) {
DOM.addClass(container, className);
}
}
/**
* container style
* @param style
*/
public setStyle(style?: string | null) {
const container = this.container;
if (style) {
container.setAttribute('style', style);
} else {
container.removeAttribute('style');
}
}
/**
* DOM position
* @protected
*/
protected insertContainer() {
const container = this.container;
const position = this.controlOption.position;
const corner = this.controlService.controlCorners[position];
if (position.indexOf('bottom') !== -1) {
corner.insertBefore(container, corner.firstChild);
} else {
corner.appendChild(container);
}
}
/**
* option keys
* @param option
* @param keys
* @protected
*/
protected checkUpdateOption(option: Partial<O>, keys: Array<keyof O>) {
return keys.some((key) => key in option);
}
}

View File

@ -0,0 +1,4 @@
export * from './control';
export * from './buttonControl';
export * from './popperControl';
export * from './selectControl';

View File

@ -0,0 +1,111 @@
import { PositionName } from '@antv/l7-core';
import { Popper, PopperPlacement, PopperTrigger } from '../../utils/popper';
import ButtonControl, { IButtonControlOption } from './buttonControl';
export { PopperControl };
export interface IPopperControlOption extends IButtonControlOption {
popperPlacement: PopperPlacement;
popperClassName?: string;
popperTrigger: PopperTrigger;
}
const PopperPlacementMap: Record<PositionName, PopperPlacement> = {
topleft: 'right-start',
topcenter: 'bottom',
topright: 'left-start',
bottomleft: 'right-end',
bottomcenter: 'top',
bottomright: 'left-end',
lefttop: 'bottom-start',
leftcenter: 'right',
leftbottom: 'top-start',
righttop: 'bottom-end',
rightcenter: 'left',
rightbottom: 'top-end',
};
export default class PopperControl<
O extends IPopperControlOption = IPopperControlOption
> extends ButtonControl<O> {
/**
*
* @protected
*/
protected popper!: Popper;
public getPopper() {
return this.popper;
}
public hide() {
this.popper.hide();
super.hide();
}
/**
*
* @param option
*/
public getDefault(option?: Partial<O>): O {
const defaultOption = super.getDefault(option);
const position = option?.position ?? defaultOption.position!;
return {
...super.getDefault(option),
popperPlacement: PopperPlacementMap[position],
popperTrigger: 'click',
};
}
public onAdd(): HTMLElement {
const button = super.onAdd();
this.initPopper();
return button;
}
public onRemove() {
this.popper.destroy();
}
public initPopper() {
const {
popperClassName,
popperPlacement,
popperTrigger,
} = this.controlOption;
const popperContainer = this.mapsService.getMapContainer()!;
this.popper = new Popper(this.button!, {
className: popperClassName,
placement: popperPlacement,
trigger: popperTrigger,
container: popperContainer,
unique: true,
});
this.popper
.on('show', () => {
this.emit('popperShow', this);
})
.on('hide', () => {
this.emit('popperHide', this);
});
return this.popper;
}
public setOptions(option: Partial<O>) {
super.setOptions(option);
if (
this.checkUpdateOption(option, [
'popperPlacement',
'popperTrigger',
'popperClassName',
])
) {
const content = this.popper.getContent();
this.popper.destroy();
this.initPopper();
this.popper.setContent(content);
}
}
}

View File

@ -0,0 +1,220 @@
import { DOM } from '@antv/l7-utils';
import { IPopperControlOption, PopperControl } from './popperControl';
type BaseOptionItem = {
value: string;
text: string;
[key: string]: string;
};
type NormalOptionItem = BaseOptionItem & {
icon?: HTMLElement;
};
type ImageOptionItem = BaseOptionItem & {
img: string;
};
export type ControlOptionItem = ImageOptionItem | NormalOptionItem;
export interface ISelectControlOption extends IPopperControlOption {
options: ControlOptionItem[];
defaultValue?: string | string[];
}
export { SelectControl };
enum SelectControlConstant {
ActiveOptionClassName = 'l7-select-control-item-active',
OptionValueAttrKey = 'data-option-value',
OptionIndexAttrKey = 'data-option-index',
}
export default class SelectControl<
O extends ISelectControlOption = ISelectControlOption
> extends PopperControl<O> {
/**
*
* @protected
*/
protected selectValue: string[] = [];
/**
* DOM
* @protected
*/
protected optionDOMList: HTMLElement[];
public setOptions(option: Partial<O>) {
super.setOptions(option);
const { options } = option;
if (options) {
this.popper.setContent(this.getPopperContent(options));
}
}
public onAdd() {
const button = super.onAdd();
const { defaultValue } = this.controlOption;
if (defaultValue) {
this.selectValue = this.transSelectValue(defaultValue);
}
this.popper.setContent(this.getPopperContent(this.controlOption.options));
return button;
}
public getSelectValue() {
return this.getIsMultiple() ? this.selectValue : this.selectValue[0];
}
public setSelectValue(value: string | string[], emitEvent = true) {
const finalValue = this.transSelectValue(value);
this.optionDOMList.forEach((optionDOM) => {
const optionValue = optionDOM.getAttribute(
SelectControlConstant.OptionValueAttrKey,
)!;
const checkboxDOM = this.getIsMultiple()
? optionDOM.querySelector('input[type=checkbox]')
: undefined;
if (finalValue.includes(optionValue)) {
DOM.addClass(optionDOM, SelectControlConstant.ActiveOptionClassName);
if (checkboxDOM) {
// @ts-ignore
DOM.setChecked(checkboxDOM, true);
}
} else {
DOM.removeClass(optionDOM, SelectControlConstant.ActiveOptionClassName);
if (checkboxDOM) {
// @ts-ignore
DOM.setChecked(checkboxDOM, false);
}
}
});
this.selectValue = finalValue;
if (emitEvent) {
this.emit(
'selectChange',
this.getIsMultiple() ? finalValue : finalValue[0],
);
}
}
/**
*
* @protected
*/
protected getIsMultiple() {
return false;
}
protected getPopperContent(options: ControlOptionItem[]): HTMLElement {
const isImageOptions = this.isImageOptions();
const content = DOM.create(
'div',
isImageOptions ? 'l7-select-control--image' : 'l7-select-control--normal',
) as HTMLElement;
if (this.getIsMultiple()) {
DOM.addClass(content, 'l7-select-control--multiple');
}
const optionsDOMList = options.map((option, optionIndex) => {
const optionDOM = isImageOptions
? // @ts-ignore
this.createImageOption(option)
: this.createNormalOption(option);
optionDOM.setAttribute(
SelectControlConstant.OptionValueAttrKey,
option.value,
);
optionDOM.setAttribute(
SelectControlConstant.OptionIndexAttrKey,
window.String(optionIndex),
);
optionDOM.addEventListener('click', this.onItemClick.bind(this, option));
return optionDOM;
});
content.append(...optionsDOMList);
this.optionDOMList = optionsDOMList;
return content;
}
protected createNormalOption = (option: NormalOptionItem) => {
const isSelect = this.selectValue.includes(option.value);
const optionDOM = DOM.create(
'div',
`l7-select-control-item ${
isSelect ? SelectControlConstant.ActiveOptionClassName : ''
}`,
) as HTMLElement;
if (this.getIsMultiple()) {
optionDOM.appendChild(this.createCheckbox(isSelect));
}
if (option.icon) {
optionDOM.appendChild(option.icon);
}
const textDOM = DOM.create('span');
textDOM.innerText = option.text;
optionDOM.appendChild(textDOM);
return optionDOM;
};
protected createImageOption(option: ImageOptionItem): HTMLElement {
const isSelect = this.selectValue.includes(option.value);
const optionDOM = DOM.create(
'div',
`l7-select-control-item ${
isSelect ? SelectControlConstant.ActiveOptionClassName : ''
}`,
) as HTMLElement;
const imgDOM = DOM.create('img') as HTMLElement;
imgDOM.setAttribute('src', option.img);
DOM.setUnDraggable(imgDOM);
optionDOM.appendChild(imgDOM);
const rowDOM = DOM.create(
'div',
'l7-select-control-item-row',
) as HTMLElement;
if (this.getIsMultiple()) {
optionDOM.appendChild(this.createCheckbox(isSelect));
}
const textDOM = DOM.create('span');
textDOM.innerText = option.text;
rowDOM.appendChild(textDOM);
optionDOM.appendChild(rowDOM);
return optionDOM;
}
protected createCheckbox(isSelect: boolean) {
const checkboxDOM = DOM.create('input') as HTMLElement;
checkboxDOM.setAttribute('type', 'checkbox');
if (isSelect) {
DOM.setChecked(checkboxDOM, true);
}
return checkboxDOM;
}
protected onItemClick = (item: ControlOptionItem) => {
if (this.getIsMultiple()) {
const targetIndex = this.selectValue.findIndex(
(value) => value === item.value,
);
if (targetIndex > -1) {
this.selectValue.splice(targetIndex, 1);
} else {
this.selectValue = [...this.selectValue, item.value];
}
} else {
this.selectValue = [item.value];
}
this.setSelectValue(this.selectValue);
};
protected isImageOptions() {
// @ts-ignore
return !!this.controlOption.options.find((item) => item.img);
}
protected transSelectValue(value: string | string[]) {
return Array.isArray(value) ? value : [value];
}
}

View File

@ -0,0 +1,73 @@
import { createL7Icon } from '../utils/icon';
import ButtonControl, {
IButtonControlOption,
} from './baseControl/buttonControl';
export interface IExportImageControlOption extends IButtonControlOption {
imageType: 'png' | 'jpeg';
onExport: (base64: string) => void;
}
export { ExportImage };
export default class ExportImage extends ButtonControl<
IExportImageControlOption
> {
public onAdd(): HTMLElement {
const button = super.onAdd();
button.addEventListener('click', this.onClick);
return button;
}
public getDefault(
option?: Partial<IExportImageControlOption>,
): IExportImageControlOption {
return {
...super.getDefault(option),
title: '导出图片',
btnIcon: createL7Icon('l7-icon-export-picture'),
imageType: 'png',
};
}
public getImage() {
const mapImage = this.mapsService.exportMap('png');
const layerImage = this.scene.exportPng('png');
return this.mergeImage(mapImage, layerImage);
}
protected onClick = async () => {
const { onExport } = this.controlOption;
onExport?.(await this.getImage());
};
/**
*
* @protected
* @param base64List
*/
protected mergeImage = async (...base64List: string[]) => {
const { imageType } = this.controlOption;
const { width = 0, height = 0 } =
this.mapsService.getContainer()?.getBoundingClientRect() ?? {};
const canvas = document.createElement('canvas') as HTMLCanvasElement;
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
const imgList = await Promise.all(
base64List.map((base64) => {
return new Promise<HTMLImageElement>((resolve) => {
const img = new Image();
img.onload = () => {
resolve(img);
};
img.src = base64;
});
}),
);
imgList.forEach((img) => {
context?.drawImage(img, 0, 0, width, height);
});
return canvas.toDataURL(`image/${imageType}`) as string;
};
}

View File

@ -0,0 +1,111 @@
import { DOM } from '@antv/l7-utils';
import { createL7Icon } from '../utils/icon';
import ScreenFull from '../utils/screenfull';
import ButtonControl, {
IButtonControlOption,
} from './baseControl/buttonControl';
export interface IFullscreenControlOption extends IButtonControlOption {
exitBtnText: IButtonControlOption['btnText'];
exitBtnIcon: IButtonControlOption['btnIcon'];
exitTitle: IButtonControlOption['title'];
}
export { Fullscreen };
export default class Fullscreen extends ButtonControl<
IFullscreenControlOption
> {
protected isFullscreen = false;
protected mapContainer: HTMLElement;
constructor(option?: Partial<IFullscreenControlOption>) {
super(option);
if (!ScreenFull.isEnabled) {
console.warn('当前浏览器环境不支持对地图全屏化');
}
}
public setOptions(newOptions: Partial<IFullscreenControlOption>) {
const { exitBtnText, exitBtnIcon, exitTitle } = newOptions;
if (this.isFullscreen) {
if (this.checkUpdateOption(newOptions, ['exitBtnIcon'])) {
this.setBtnIcon(exitBtnIcon);
}
if (this.checkUpdateOption(newOptions, ['exitBtnText'])) {
this.setBtnText(exitBtnText);
}
if (this.checkUpdateOption(newOptions, ['exitTitle'])) {
this.setBtnTitle(exitTitle);
}
}
super.setOptions(newOptions);
}
public onAdd(): HTMLElement {
const button = super.onAdd();
button.addEventListener('click', this.onClick);
this.mapContainer = DOM.getContainer(this.scene.getSceneConfig().id!);
this.mapContainer.addEventListener(
'fullscreenchange',
this.onFullscreenChange,
);
return button;
}
public onRemove() {
super.onRemove();
this.mapContainer.removeEventListener(
'fullscreenchange',
this.onFullscreenChange,
);
}
public getDefault(
option?: Partial<IFullscreenControlOption>,
): IFullscreenControlOption {
return {
...super.getDefault(option),
title: '全屏',
btnIcon: createL7Icon('l7-icon-fullscreen'),
exitTitle: '退出全屏',
exitBtnIcon: createL7Icon('l7-icon-exit-fullscreen'),
};
}
public toggleFullscreen = async () => {
if (ScreenFull.isEnabled) {
await ScreenFull.toggle(this.mapContainer);
}
};
protected onClick = () => {
this.toggleFullscreen();
};
protected onFullscreenChange = () => {
this.isFullscreen = !!document.fullscreenElement;
const {
btnText,
btnIcon,
title,
exitBtnText,
exitBtnIcon,
exitTitle,
} = this.controlOption;
if (this.isFullscreen) {
this.setBtnTitle(exitTitle);
this.setBtnText(exitBtnText);
this.setBtnIcon(exitBtnIcon);
} else {
this.setBtnTitle(title);
this.setBtnText(btnText);
this.setBtnIcon(btnIcon);
}
this.emit('fullscreenChange', this.isFullscreen);
};
}

View File

@ -0,0 +1,70 @@
import { Point } from '@antv/l7-core';
import { isNaN } from 'lodash';
import { createL7Icon } from '../utils/icon';
import ButtonControl, {
IButtonControlOption,
} from './baseControl/buttonControl';
export interface IGeoLocateOption extends IButtonControlOption {
transform: (position: Point) => Point | Promise<Point>;
}
export { GeoLocate };
export default class GeoLocate extends ButtonControl<IGeoLocateOption> {
constructor(option?: Partial<IGeoLocateOption>) {
super(option);
if (!window.navigator.geolocation) {
console.warn('当前浏览器环境不支持获取地理定位');
}
}
public getDefault(option?: Partial<IGeoLocateOption>): IGeoLocateOption {
return {
...super.getDefault(option),
title: '定位',
btnIcon: createL7Icon('l7-icon-reposition'),
};
}
public onAdd(): HTMLElement {
const button = super.onAdd();
button.addEventListener('click', this.onClick);
return button;
}
/**
* API
*/
public getGeoLocation = () => {
return new Promise<Point>((resolve, reject) => {
window.navigator.geolocation.getCurrentPosition(
({ coords }) => {
const { longitude, latitude } = coords ?? {};
if (!isNaN(longitude) && !isNaN(latitude)) {
resolve([longitude, latitude]);
} else {
reject();
}
},
(e) => {
reject(e);
},
);
});
};
public onClick = async () => {
if (!window.navigator.geolocation) {
return;
}
const { transform } = this.controlOption;
const position = await this.getGeoLocation();
const currentZoom = this.mapsService.getZoom();
this.mapsService.setZoomAndCenter(
currentZoom > 15 ? currentZoom : 15,
transform ? await transform(position) : position,
);
};
}

View File

@ -1,317 +0,0 @@
import { PositionType } from '@antv/l7-core';
import { bindAll, DOM } from '@antv/l7-utils';
import { ILayerControlOption } from '../interface';
import Control from './BaseControl';
interface IInputItem extends HTMLInputElement {
layerId: string;
}
export default class Layers extends Control {
private layerControlInputs: any[];
private layers: any[];
private lastZIndex: number;
private handlingClick: boolean;
private layersLink: HTMLElement;
private baseLayersList: HTMLElement;
private separator: HTMLElement;
private overlaysList: HTMLElement;
private form: HTMLElement;
constructor(cfg: Partial<ILayerControlOption>) {
super(cfg);
this.layerControlInputs = [];
this.layers = [];
this.lastZIndex = 0;
this.handlingClick = false;
this.initLayers();
bindAll(
[
'checkDisabledLayers',
'onLayerChange',
'collapse',
'extend',
'expand',
'onInputClick',
],
this,
);
}
public getDefault() {
return {
collapsed: true,
position: PositionType.TOPRIGHT,
autoZIndex: true,
hideSingleBase: false,
sortLayers: false,
name: 'layers',
};
}
public onAdd() {
this.initLayout();
this.update();
this.mapsService.on('zoomend', this.checkDisabledLayers);
this.layers.forEach((layerItem) => {
layerItem.layer.on('remove', this.onLayerChange);
layerItem.layer.on('add', this.onLayerChange);
});
return this.container;
}
public addVisualLayer(layer: any, name: string | number) {
this.addLayer(layer, name, true);
return this.mapsService ? this.update() : this;
}
public expand() {
const { height } = this.renderService.getViewportSize();
DOM.addClass(this.container, 'l7-control-layers-expanded');
this.form.style.height = 'null';
const acceptableHeight = height - (this.container.offsetTop + 50);
if (acceptableHeight < this.form.clientHeight) {
DOM.addClass(this.form, 'l7-control-layers-scrollbar');
this.form.style.height = acceptableHeight + 'px';
} else {
DOM.removeClass(this.form, 'l7-control-layers-scrollbar');
}
this.checkDisabledLayers();
return this;
}
public collapse() {
DOM.removeClass(this.container, 'l7-control-layers-expanded');
return this;
}
public onRemove() {
if (!this.mapsService) {
return;
}
this.mapsService.off('click', this.collapse);
this.layers.forEach((layerItem) => {
layerItem.layer.off('remove', this.onLayerChange);
layerItem.layer.off('add', this.onLayerChange);
});
}
private initLayout() {
const className = 'l7-control-layers';
const container = (this.container = DOM.create('div', className));
const { collapsed } = this.controlOption;
// makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
container.setAttribute('aria-haspopup', 'true');
const form = (this.form = DOM.create(
'form',
className + '-list',
) as HTMLElement);
if (collapsed) {
this.mapsService.on('click', this.collapse);
container.addEventListener('mouseenter', this.expand);
container.addEventListener('mouseleave', this.collapse);
}
this.layersLink = DOM.create('a', className + '-toggle', container);
const link = this.layersLink;
// link.href = '#';
link.title = 'Layers';
if (!collapsed) {
this.expand();
}
this.baseLayersList = DOM.create('div', className + '-base', form);
this.separator = DOM.create('div', className + '-separator', form);
this.overlaysList = DOM.create('div', className + '-overlays', form);
container.appendChild(form);
}
private initLayers() {
const { baseLayers = {}, overlayers = {} } = this.controlOption;
Object.keys(baseLayers).forEach((name: string) => {
// baseLayers[name].once('inited', this.update);
this.addLayer(baseLayers[name], name, false);
});
Object.keys(overlayers).forEach((name: any) => {
// overlayers[name].once('inited', this.update);
this.addLayer(overlayers[name], name, true);
});
}
private update() {
if (!this.container) {
return this;
}
DOM.empty(this.baseLayersList);
DOM.empty(this.overlaysList);
this.layerControlInputs = [];
let baseLayersPresent;
let overlaysPresent;
let i;
let obj;
let baseLayersCount = 0;
for (i = 0; i < this.layers.length; i++) {
obj = this.layers[i];
this.addItem(obj);
overlaysPresent = overlaysPresent || obj.overlay;
baseLayersPresent = baseLayersPresent || !obj.overlay;
baseLayersCount += !obj.overlay ? 1 : 0;
}
// Hide base layers section if there's only one layer.
if (this.controlOption.hideSingleBase) {
baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
this.baseLayersList.style.display = baseLayersPresent ? '' : 'none';
}
this.separator.style.display =
overlaysPresent && baseLayersPresent ? '' : 'none';
return this;
}
private checkDisabledLayers() {
const inputs = this.layerControlInputs;
let input: IInputItem;
let layer;
const zoom = this.mapsService.getZoom();
for (let i = inputs.length - 1; i >= 0; i--) {
input = inputs[i];
layer = this.layerService.getLayer(input.layerId);
if (layer && layer.inited) {
const minZoom = layer.getMinZoom();
const maxZoom = layer.getMaxZoom();
input.disabled = zoom < minZoom || zoom > maxZoom;
}
}
}
private addLayer(layer: any, name: string | number, overlay: boolean) {
if (this.mapsService) {
layer.on('add', this.onLayerChange);
layer.on('remove', this.onLayerChange);
}
this.layers.push({
layer,
name,
overlay,
});
const { sortLayers, sortFunction, autoZIndex } = this.controlOption;
if (sortLayers) {
this.layers.sort((a, b) => {
return sortFunction(a.layer, b.layer, a.name, b.name);
});
}
if (autoZIndex && layer.setZIndex) {
this.lastZIndex++;
layer.setZIndex(this.lastZIndex);
}
this.expandIfNotCollapsed();
}
private expandIfNotCollapsed() {
if (this.mapsService && !this.controlOption.collapsed) {
this.expand();
}
return this;
}
private onLayerChange(e: any) {
if (!this.handlingClick) {
this.update();
}
const obj = this.layerService.getLayer(e.target.layerId);
// @ts-ignore
const type = obj?.overlay
? e.type === 'add'
? 'overlayadd'
: 'overlayremove'
: e.type === 'add'
? 'baselayerchange'
: null;
if (type) {
this.emit(type, obj);
}
}
private createRadioElement(name: string, checked: boolean): ChildNode {
const radioHtml =
'<input type="radio" class="l7-control-layers-selector" name="' +
name +
'"' +
(checked ? ' checked="checked"' : '') +
'/>';
const radioFragment = document.createElement('div');
radioFragment.innerHTML = radioHtml;
return radioFragment.firstChild as ChildNode;
}
private addItem(obj: any) {
const label = document.createElement('label');
const layer = this.layerService.getLayer(obj.layer.id);
const checked = layer && layer.inited && obj.layer.isVisible();
let input: IInputItem;
if (obj.overlay) {
input = document.createElement('input') as IInputItem;
input.type = 'checkbox';
input.className = 'l7-control-layers-selector';
input.defaultChecked = checked;
} else {
input = this.createRadioElement('l7-base-layers', checked) as IInputItem;
}
this.layerControlInputs.push(input);
input.layerId = obj.layer.id;
input.addEventListener('click', this.onInputClick);
const name = document.createElement('span');
name.innerHTML = ' ' + obj.name;
const holder = document.createElement('div');
label.appendChild(holder);
holder.appendChild(input);
holder.appendChild(name);
const container = obj.overlay ? this.overlaysList : this.baseLayersList;
container.appendChild(label);
this.checkDisabledLayers();
return label;
}
private onInputClick() {
const inputs = this.layerControlInputs;
let input;
let layer;
const addedLayers = [];
const removedLayers = [];
this.handlingClick = true;
for (let i = inputs.length - 1; i >= 0; i--) {
input = inputs[i];
layer = this.layerService.getLayer(input.layerId);
if (input.checked) {
addedLayers.push(layer);
} else if (!input.checked) {
removedLayers.push(layer);
}
}
removedLayers.forEach((l: any) => {
l.hide();
});
addedLayers.forEach((l: any) => {
l.show();
});
this.handlingClick = false;
}
}

View File

@ -0,0 +1,126 @@
import { ILayer } from '@antv/l7-core';
import { createL7Icon } from '../utils/icon';
import SelectControl, {
ISelectControlOption,
ControlOptionItem,
} from './baseControl/selectControl';
export interface ILayerControlOption extends ISelectControlOption {
layers: ILayer[];
}
export { LayerControl };
export default class LayerControl extends SelectControl<ILayerControlOption> {
protected get layers() {
return this.controlOption.layers || this.layerService.getLayers() || [];
}
public getDefault(
option?: Partial<ILayerControlOption>,
): ILayerControlOption {
return {
...super.getDefault(option),
title: '图层控制',
btnIcon: createL7Icon('l7-icon-layer'),
options: [],
};
}
public getLayerVisible() {
return this.layers
.filter((layer) => {
return layer.isVisible();
})
.map((layer) => {
return layer.name;
});
}
public getLayerOptions(): ControlOptionItem[] {
return this.layers.map((layer: ILayer) => {
return {
text: layer.name,
value: layer.name,
};
});
}
public setOptions(option: Partial<ILayerControlOption>) {
const isLayerChange = this.checkUpdateOption(option, ['layers']);
if (isLayerChange) {
this.unbindLayerVisibleCallback();
}
super.setOptions(option);
if (isLayerChange) {
this.bindLayerVisibleCallback();
this.selectValue = this.getLayerVisible();
this.controlOption.options = this.getLayerOptions();
this.popper.setContent(this.getPopperContent(this.controlOption.options));
}
}
public onAdd(): HTMLElement {
if (!this.controlOption.options?.length) {
this.controlOption.options = this.getLayerOptions();
}
if (!this.controlOption.defaultValue) {
this.controlOption.defaultValue = this.getLayerVisible();
}
this.on('selectChange', this.onSelectChange);
this.layerService.on('layerChange', this.onLayerChange);
this.bindLayerVisibleCallback();
return super.onAdd();
}
public bindLayerVisibleCallback = () => {
this.layers.forEach((layer) => {
layer.on('show', this.onLayerVisibleChane);
layer.on('hide', this.onLayerVisibleChane);
});
};
public unbindLayerVisibleCallback = () => {
this.layers.forEach((layer) => {
layer.off('show', this.onLayerVisibleChane);
layer.off('hide', this.onLayerVisibleChane);
});
};
public onRemove() {
this.off('selectChange', this.onSelectChange);
this.layerService.off('layerChange', this.onLayerChange);
this.unbindLayerVisibleCallback();
}
protected onLayerChange = () => {
if (this.controlOption.layers?.length) {
return;
}
this.selectValue = this.getLayerVisible();
this.setOptions({
options: this.getLayerOptions(),
});
};
protected onLayerVisibleChane = () => {
this.setSelectValue(this.getLayerVisible());
};
protected onSelectChange = () => {
this.layers.forEach((layer) => {
const needShow = this.selectValue.includes(layer.name);
const isShow = layer.isVisible();
if (needShow && !isShow) {
layer.show();
}
if (!needShow && isShow) {
layer.hide();
}
});
};
protected getIsMultiple(): boolean {
return true;
}
}

View File

@ -1,30 +1,63 @@
import { DOM } from '@antv/l7-utils';
import Control, { PositionType } from './BaseControl';
import { Control, IControlOption, PositionType } from './baseControl';
export default class Logo extends Control {
public getDefault() {
export interface ILogoControlOption extends IControlOption {
// Logo 展示的图片 url
img: string;
// 点击 Logo 跳转的超链接,不传或传 '' | null 则纯展示 Logo点击不跳转
href?: string | null;
}
export { Logo };
export default class Logo extends Control<ILogoControlOption> {
public getDefault(): ILogoControlOption {
return {
position: PositionType.BOTTOMLEFT,
name: 'logo',
href: 'https://l7.antv.vision/',
img:
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*GRb1TKp4HcMAAAAAAAAAAAAAARQnAQ',
};
}
public onAdd() {
const className = 'l7-control-logo';
const container = DOM.create('div', className);
const anchor: HTMLLinkElement = DOM.create(
'a',
'l7-ctrl-logo',
) as HTMLLinkElement;
anchor.target = '_blank';
anchor.rel = 'noopener nofollow';
anchor.href = 'https://antv.alipay.com/l7';
anchor.setAttribute('aria-label', 'AntV logo');
anchor.setAttribute('rel', 'noopener nofollow');
container.appendChild(anchor);
const container = DOM.create('div', 'l7-control-logo');
this.setLogoContent(container);
return container;
}
public onRemove() {
return null;
}
public setOptions(option: Partial<ILogoControlOption>) {
super.setOptions(option);
if (this.checkUpdateOption(option, ['img', 'href'])) {
DOM.clearChildren(this.container);
this.setLogoContent(this.container);
}
}
protected setLogoContent(container: HTMLElement) {
const { href, img } = this.controlOption;
const imgDOM = DOM.create('img') as HTMLElement;
imgDOM.setAttribute('src', img);
imgDOM.setAttribute('aria-label', 'AntV logo');
DOM.setUnDraggable(imgDOM);
if (href) {
const anchorDOM = DOM.create(
'a',
'l7-control-logo-link',
) as HTMLLinkElement;
anchorDOM.target = '_blank';
anchorDOM.href = href;
anchorDOM.rel = 'noopener nofollow';
anchorDOM.setAttribute('rel', 'noopener nofollow');
anchorDOM.appendChild(imgDOM);
container.appendChild(anchorDOM);
} else {
container.appendChild(imgDOM);
}
}
}

View File

@ -0,0 +1,78 @@
import { GaodeMapStyleConfig, MapboxMapStyleConfig } from '../constants';
import { createL7Icon } from '../utils/icon';
import SelectControl, {
ControlOptionItem,
ISelectControlOption,
} from './baseControl/selectControl';
export { MapTheme };
export default class MapTheme extends SelectControl<ISelectControlOption> {
public getDefault(
option?: Partial<ISelectControlOption>,
): ISelectControlOption {
return {
...super.getDefault(option),
title: '地图样式',
btnIcon: createL7Icon('l7-icon-color'),
options: [],
};
}
public getStyleOptions(): ControlOptionItem[] {
const mapStyleConfig =
this.mapsService.getType() === 'mapbox'
? MapboxMapStyleConfig
: GaodeMapStyleConfig;
return Object.entries(this.mapsService.getMapStyleConfig())
.filter(([key, value]) => typeof value === 'string' && key !== 'blank')
.map(([key, value]) => {
// @ts-ignore
const { text, img } = mapStyleConfig[key] ?? {};
return {
text: text ?? key,
value,
img,
key,
};
});
}
public getMapStyle() {
return this.mapsService.getMapStyle();
}
public onAdd(): HTMLElement {
if (!this.controlOption.options?.length) {
this.controlOption.options = this.getStyleOptions();
}
if (this.controlOption.defaultValue) {
const defaultValue = this.controlOption.defaultValue as string;
this.controlOption.defaultValue =
this.controlOption.options.find((item) => item.key === defaultValue)
?.value ?? defaultValue;
} else {
const defaultStyle = this.getMapStyle();
if (defaultStyle) {
this.controlOption.defaultValue = defaultStyle;
} else {
// @ts-ignore
this.mapsService.map.once('styledata', () => {
const mapboxStyle = this.mapsService.getMapStyle();
this.controlOption.defaultValue = mapboxStyle;
this.setSelectValue(mapboxStyle, false);
});
}
}
this.on('selectChange', this.onMapThemeChange);
return super.onAdd();
}
protected onMapThemeChange = () => {
this.mapsService.setMapStyle(this.selectValue[0]);
};
protected getIsMultiple(): boolean {
return false;
}
}

View File

@ -0,0 +1,60 @@
import { ILngLat, Position, PositionType } from '@antv/l7-core';
import { DOM } from '@antv/l7-utils';
import Control, { IControlOption } from './baseControl/control';
export interface IMouseLocationControlOption extends IControlOption {
transform: (position: Position) => Position;
}
export { MouseLocation };
export default class MouseLocation extends Control<
IMouseLocationControlOption
> {
protected location: Position = [0, 0];
public getLocation() {
return this.location;
}
public getDefault(
option?: Partial<IMouseLocationControlOption>,
): IMouseLocationControlOption {
return {
...super.getDefault(option),
position: PositionType.BOTTOMLEFT,
transform: ([lng, lat]) => {
return [+(+lng).toFixed(6), +(+lat).toFixed(6)];
},
};
}
public onAdd(): HTMLElement {
const container = DOM.create('div', 'l7-control-mouse-location');
container.innerHTML = '&nbsp;';
this.mapsService.on('mousemove', this.onMouseMove);
return container;
}
public onRemove(): void {
this.mapsService.off('mousemove', this.onMouseMove);
}
protected onMouseMove = (e: any) => {
let position: Position = this.location;
const lngLat: ILngLat | undefined = e.lngLat || e.lnglat;
const { transform } = this.controlOption;
if (lngLat) {
position = [lngLat.lng, lngLat.lat];
}
this.location = position;
if (transform) {
position = transform(position);
}
this.insertLocation2HTML(position);
this.emit('locationChange', position);
};
protected insertLocation2HTML(position: Position) {
this.container.innerText = position.join(', ');
}
}

View File

@ -1,39 +1,43 @@
import { bindAll, DOM, lnglatDistance } from '@antv/l7-utils';
import { IScaleControlOption } from '../interface';
import Control, { PositionType } from './BaseControl';
import { DOM, lnglatDistance } from '@antv/l7-utils';
import { Control, IControlOption, PositionType } from './baseControl';
export default class Scale extends Control {
export interface IScaleControlOption extends IControlOption {
lockWidth: boolean;
maxWidth: number;
metric: boolean;
updateWhenIdle: boolean;
imperial: boolean;
}
export { Scale };
export default class Scale extends Control<IScaleControlOption> {
private mScale: HTMLElement;
private iScale: HTMLElement;
constructor(cfg?: Partial<IScaleControlOption>) {
super(cfg);
bindAll(['update'], this);
}
public getDefault() {
public getDefault(option: Partial<IScaleControlOption>) {
return {
...super.getDefault(option),
name: 'scale',
position: PositionType.BOTTOMLEFT,
maxWidth: 100,
metric: true,
updateWhenIdle: false,
imperial: false,
name: 'scale',
lockWidth: true,
};
}
public onAdd() {
const className = 'l7-control-scale';
const container = DOM.create('div', className);
this.addScales(className + '-line', container);
this.resetScaleLines(container);
const { updateWhenIdle } = this.controlOption;
// TODO: 高德地图和MapBox地图事件不一致问题
// 高德zoomchange
this.mapsService.on(updateWhenIdle ? 'moveend' : 'mapmove', this.update);
this.mapsService.on(updateWhenIdle ? 'zoomend' : 'zoomchange', this.update);
this.update();
return container;
}
public onRemove() {
const { updateWhenIdle } = this.controlOption;
this.mapsService.off(
@ -42,7 +46,23 @@ export default class Scale extends Control {
);
this.mapsService.off(updateWhenIdle ? 'moveend' : 'mapmove', this.update);
}
public update() {
public setOptions(newOption: Partial<IScaleControlOption>) {
super.setOptions(newOption);
if (
this.checkUpdateOption(newOption, [
'lockWidth',
'maxWidth',
'metric',
'updateWhenIdle',
'imperial',
])
) {
this.resetScaleLines(this.container);
}
}
public update = () => {
const mapsService = this.mapsService;
const { maxWidth } = this.controlOption;
const y = mapsService.getSize()[1] / 2;
@ -51,7 +71,8 @@ export default class Scale extends Control {
const p2 = mapsService.containerToLngLat([maxWidth, y]);
const maxMeters = lnglatDistance([p1.lng, p1.lat], [p2.lng, p2.lat]);
this.updateScales(maxMeters);
}
};
public updateScales(maxMeters: number) {
const { metric, imperial } = this.controlOption;
if (metric && maxMeters) {
@ -61,11 +82,42 @@ export default class Scale extends Control {
this.updateImperial(maxMeters);
}
}
private resetScaleLines(container: HTMLElement) {
DOM.clearChildren(container);
const { metric, imperial, maxWidth, lockWidth } = this.controlOption;
if (lockWidth) {
DOM.addStyle(container, `width: ${maxWidth}px`);
}
if (metric) {
this.mScale = DOM.create('div', 'l7-control-scale-line', container);
}
if (imperial) {
this.iScale = DOM.create('div', 'l7-control-scale-line', container);
}
this.update();
}
private updateScale(scale: HTMLElement, text: string, ratio: number) {
const { maxWidth } = this.controlOption;
scale.style.width = Math.round(maxWidth * ratio) + 'px';
scale.innerHTML = text;
}
private getRoundNum(num: number) {
const pow10 = Math.pow(10, (Math.floor(num) + '').length - 1);
let d = num / pow10;
d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
return pow10 * d;
}
private updateMetric(maxMeters: number) {
const meters = this.getRoundNum(maxMeters);
const label = meters < 1000 ? meters + ' m' : meters / 1000 + ' km';
this.updateScale(this.mScale, label, meters / maxMeters);
}
private updateImperial(maxMeters: number) {
const maxFeet = maxMeters * 3.2808399;
let maxMiles: number;
@ -81,26 +133,4 @@ export default class Scale extends Control {
this.updateScale(this.iScale, feet + ' ft', feet / maxFeet);
}
}
private updateScale(scale: HTMLElement, text: string, ratio: number) {
const { maxWidth } = this.controlOption;
scale.style.width = Math.round(maxWidth * ratio) + 'px';
scale.innerHTML = text;
}
private getRoundNum(num: number) {
const pow10 = Math.pow(10, (Math.floor(num) + '').length - 1);
let d = num / pow10;
d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
return pow10 * d;
}
private addScales(className: string, container: HTMLElement) {
const { metric, imperial } = this.controlOption;
if (metric) {
this.mScale = DOM.create('div', className, container);
}
if (imperial) {
this.iScale = DOM.create('div', className, container);
}
}
}

View File

@ -1,48 +1,53 @@
import { bindAll, DOM } from '@antv/l7-utils';
import { IZoomControlOption } from '../interface';
import Control, { PositionType } from './BaseControl';
import { PositionType } from '@antv/l7-core';
import { DOM } from '@antv/l7-utils';
import { ELType } from '@antv/l7-utils/src/dom';
import { createL7Icon } from '../utils/icon';
import { Control, IControlOption } from './baseControl';
export default class Zoom extends Control {
export interface IZoomControlOption extends IControlOption {
zoomInText: ELType | string;
zoomInTitle: string;
zoomOutText: ELType | string;
zoomOutTitle: string;
}
export { Zoom };
export default class Zoom extends Control<IZoomControlOption> {
private disabled: boolean;
private zoomInButton: HTMLElement;
private zoomOutButton: HTMLElement;
constructor(cfg?: Partial<IZoomControlOption>) {
super(cfg);
bindAll(['updateDisabled', 'zoomIn', 'zoomOut'], this);
}
public getDefault() {
public getDefault(option: Partial<IZoomControlOption>) {
return {
position: PositionType.TOPLEFT,
zoomInText: '+',
zoomInTitle: 'Zoom in',
zoomOutText: '&#x2212;',
zoomOutTitle: 'Zoom out',
...super.getDefault(option),
position: PositionType.BOTTOMRIGHT,
name: 'zoom',
zoomInText: createL7Icon('l7-icon-enlarge'),
zoomInTitle: 'Zoom in',
zoomOutText: createL7Icon('l7-icon-narrow'),
zoomOutTitle: 'Zoom out',
};
}
public setOptions(newOptions: Partial<IZoomControlOption>) {
super.setOptions(newOptions);
if (
this.checkUpdateOption(newOptions, [
'zoomInText',
'zoomInTitle',
'zoomOutText',
'zoomOutTitle',
])
) {
this.resetButtonGroup(this.container);
}
}
public onAdd(): HTMLElement {
const zoomName = 'l7-control-zoom';
const container = DOM.create('div', zoomName + ' l7-bar');
this.zoomInButton = this.createButton(
this.controlOption.zoomInText,
this.controlOption.zoomInTitle,
zoomName + '-in',
container,
this.zoomIn,
);
this.zoomOutButton = this.createButton(
this.controlOption.zoomOutText,
this.controlOption.zoomOutTitle,
zoomName + '-out',
container,
this.zoomOut,
);
const container = DOM.create('div', 'l7-control-zoom');
this.resetButtonGroup(container);
this.mapsService.on('zoomend', this.updateDisabled);
this.mapsService.on('zoomchange', this.updateDisabled);
this.updateDisabled();
return container;
}
@ -63,46 +68,70 @@ export default class Zoom extends Control {
return this;
}
private zoomIn() {
public zoomIn = () => {
if (
!this.disabled &&
this.mapsService.getZoom() < this.mapsService.getMaxZoom()
) {
this.mapsService.zoomIn();
}
}
private zoomOut() {
};
public zoomOut = () => {
if (
!this.disabled &&
this.mapsService.getZoom() > this.mapsService.getMinZoom()
) {
this.mapsService.zoomOut();
}
};
private resetButtonGroup(container: HTMLElement) {
DOM.clearChildren(container);
this.zoomInButton = this.createButton(
this.controlOption.zoomInText,
this.controlOption.zoomInTitle,
'l7-button-control',
container,
this.zoomIn,
);
this.zoomOutButton = this.createButton(
this.controlOption.zoomOutText,
this.controlOption.zoomOutTitle,
'l7-button-control',
container,
this.zoomOut,
);
this.updateDisabled();
}
private createButton(
html: string,
html: ELType | string,
tile: string,
className: string,
container: HTMLElement,
fn: (...arg: any[]) => any,
) {
const link = DOM.create('a', className, container) as HTMLLinkElement;
const link = DOM.create('button', className, container) as HTMLLinkElement;
if (typeof html === 'string') {
link.innerHTML = html;
} else {
link.append(html);
}
link.title = tile;
link.href = 'javascript:void(0)';
link.addEventListener('click', fn);
return link;
}
private updateDisabled() {
private updateDisabled = () => {
const mapsService = this.mapsService;
const className = 'l7-disabled';
DOM.removeClass(this.zoomInButton, className);
DOM.removeClass(this.zoomOutButton, className);
this.zoomInButton.removeAttribute('disabled');
this.zoomOutButton.removeAttribute('disabled');
if (this.disabled || mapsService.getZoom() <= mapsService.getMinZoom()) {
DOM.addClass(this.zoomOutButton, className);
this.zoomOutButton.setAttribute('disabled', 'true');
}
if (this.disabled || mapsService.getZoom() >= mapsService.getMaxZoom()) {
DOM.addClass(this.zoomInButton, className);
}
this.zoomInButton.setAttribute('disabled', 'true');
}
};
}

View File

@ -0,0 +1,70 @@
@import 'variables.less';
.l7-button-control {
min-width: @l7-btn-control-size;
height: @l7-btn-control-size;
background-color: @l7-control-bg-color;
border-width: 0;
border-radius: @l7-btn-control-border-radius;
outline: 0;
cursor: pointer;
transition: all 0.2s;
display: flex;
justify-content: center;
align-items: center;
padding: 0 ((@l7-btn-control-size - @l7-btn-icon-size) / 2);
box-shadow: @l7-control-shadow;
line-height: 16px;
.l7-iconfont {
fill: @l7-control-font-color;
color: @l7-control-font-color;
width: @l7-btn-icon-size;
height: @l7-btn-icon-size;
}
&.l7-button-control--row {
padding: 0 16px 0 13px;
* + .l7-button-control__text {
margin-left: 8px;
}
}
&.l7-button-control--column {
height: @l7-btn-column-height;
flex-direction: column;
.l7-iconfont {
margin-top: 3px;
}
.l7-button-control__text {
margin-top: 3px;
font-size: 10px;
transform: scale(0.83333);
}
}
&:not(:disabled) {
&:hover {
background-color: @l7-btn-control-bg-hover-color;
}
&:active {
background-color: @l7-btn-control-bg-active-color;
}
}
&:disabled {
background-color: @l7-btn-control-disabled-bg-color;
color: @l7-btn-control-disabled-font-color;
cursor: not-allowed;
.l7-iconfont {
fill: @l7-btn-control-disabled-font-color;
color: @l7-btn-control-disabled-font-color;
}
&:hover {
background-color: @l7-btn-control-disabled-bg-color;
}
&:active {
background-color: @l7-btn-control-disabled-bg-color;
}
}
}

View File

@ -0,0 +1,71 @@
@import 'variables.less';
.l7-control-container {
font: 12px/1.5 'Helvetica Neue', Arial, Helvetica, sans-serif;
.l7-control {
position: relative;
z-index: 800;
float: left;
clear: both;
color: @l7-control-font-color;
font-size: @l7-control-font-size;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
&.l7-control--hide {
display: none;
}
}
each(@position-list,{
.l7-@{value} {
@{value}: 0;
display: flex;
position: absolute;
z-index: 1000;
pointer-events: none;
.l7-control:not(.l7-control--hide) {
margin-@{value}: @l7-control-space;
}
}
});
.l7-center {
position: absolute;
display: flex;
justify-content: center;
&.l7-top,
&.l7-bottom {
width: 100%;
}
&.l7-left,
&.l7-right {
height: 100%;
}
.l7-control {
margin-right: @l7-control-space;
margin-bottom: @l7-control-space;
}
}
.l7-row {
flex-direction: row;
&.l7-top {
align-items: flex-start;
}
&.l7-bottom {
align-items: flex-end;
}
}
.l7-column {
flex-direction: column;
&.l7-left {
align-items: flex-start;
}
&.l7-right {
align-items: flex-end;
}
}
}

View File

@ -0,0 +1,567 @@
.l7-marker-container {
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
}
.l7-marker {
position: absolute !important;
top: 0;
left: 0;
z-index: 5;
cursor: pointer;
}
.l7-marker-cluster {
width: 40px;
height: 40px;
background-color: rgba(181, 226, 140, 0.6);
background-clip: padding-box;
border-radius: 20px;
}
.l7-marker-cluster div {
width: 30px;
height: 30px;
margin-top: 5px;
margin-left: 5px;
font: 12px 'Helvetica Neue', Arial, Helvetica, sans-serif;
text-align: center;
background-color: rgba(110, 204, 57, 0.6);
border-radius: 15px;
}
.l7-marker-cluster span {
line-height: 30px;
}
.l7-touch .l7-control-attribution,
.l7-touch .l7-control-layers,
.l7-touch .l7-bar {
box-shadow: none;
}
.l7-touch .l7-control-layers,
.l7-touch .l7-bar {
background-clip: padding-box;
border: 2px solid rgba(0, 0, 0, 0.2);
}
.mapboxgl-ctrl-logo,
.amap-logo {
display: none !important;
}
.l7-select-box {
border: 3px dashed gray;
border-radius: 2px;
position: absolute;
z-index: 1000;
box-sizing: border-box;
}
.l7-control-container {
font: 12px/1.5 'Helvetica Neue', Arial, Helvetica, sans-serif;
}
.l7-control-container .l7-control {
position: relative;
z-index: 800;
float: left;
clear: both;
color: #595959;
font-size: 12px;
pointer-events: visiblePainted;
/* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.l7-control-container .l7-control.l7-control--hide {
display: none;
}
.l7-control-container .l7-top {
top: 0;
display: flex;
position: absolute;
z-index: 1000;
pointer-events: none;
}
.l7-control-container .l7-top .l7-control:not(.l7-control--hide) {
margin-top: 8px;
}
.l7-control-container .l7-right {
right: 0;
display: flex;
position: absolute;
z-index: 1000;
pointer-events: none;
}
.l7-control-container .l7-right .l7-control:not(.l7-control--hide) {
margin-right: 8px;
}
.l7-control-container .l7-bottom {
bottom: 0;
display: flex;
position: absolute;
z-index: 1000;
pointer-events: none;
}
.l7-control-container .l7-bottom .l7-control:not(.l7-control--hide) {
margin-bottom: 8px;
}
.l7-control-container .l7-left {
left: 0;
display: flex;
position: absolute;
z-index: 1000;
pointer-events: none;
}
.l7-control-container .l7-left .l7-control:not(.l7-control--hide) {
margin-left: 8px;
}
.l7-control-container .l7-center {
position: absolute;
display: flex;
justify-content: center;
}
.l7-control-container .l7-center.l7-top,
.l7-control-container .l7-center.l7-bottom {
width: 100%;
}
.l7-control-container .l7-center.l7-left,
.l7-control-container .l7-center.l7-right {
height: 100%;
}
.l7-control-container .l7-center .l7-control {
margin-right: 8px;
margin-bottom: 8px;
}
.l7-control-container .l7-row {
flex-direction: row;
}
.l7-control-container .l7-row.l7-top {
align-items: flex-start;
}
.l7-control-container .l7-row.l7-bottom {
align-items: flex-end;
}
.l7-control-container .l7-column {
flex-direction: column;
}
.l7-control-container .l7-column.l7-left {
align-items: flex-start;
}
.l7-control-container .l7-column.l7-right {
align-items: flex-end;
}
.l7-button-control {
min-width: 28px;
height: 28px;
background-color: #fff;
border-width: 0;
border-radius: 2px;
outline: 0;
cursor: pointer;
transition: all 0.2s;
display: flex;
justify-content: center;
align-items: center;
padding: 0 6px;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.15);
line-height: 16px;
}
.l7-button-control .l7-iconfont {
fill: #595959;
color: #595959;
width: 16px;
height: 16px;
}
.l7-button-control.l7-button-control--row {
padding: 0 16px 0 13px;
}
.l7-button-control.l7-button-control--row * + .l7-button-control__text {
margin-left: 8px;
}
.l7-button-control.l7-button-control--column {
height: 44px;
flex-direction: column;
}
.l7-button-control.l7-button-control--column .l7-iconfont {
margin-top: 3px;
}
.l7-button-control.l7-button-control--column .l7-button-control__text {
margin-top: 3px;
font-size: 10px;
transform: scale(0.83333);
}
.l7-button-control:not(:disabled):hover {
background-color: #f3f3f3;
}
.l7-button-control:not(:disabled):active {
background-color: #f3f3f3;
}
.l7-button-control:disabled {
background-color: #fafafa;
color: #bdbdbd;
cursor: not-allowed;
}
.l7-button-control:disabled .l7-iconfont {
fill: #bdbdbd;
color: #bdbdbd;
}
.l7-button-control:disabled:hover {
background-color: #fafafa;
}
.l7-button-control:disabled:active {
background-color: #fafafa;
}
.l7-popper {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
z-index: 5;
color: #595959;
}
.l7-popper.l7-popper-hide {
display: none;
}
.l7-popper .l7-popper-content {
min-height: 28px;
background: #fff;
border-radius: 2px;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.15);
}
.l7-popper .l7-popper-arrow {
width: 0;
height: 0;
border-width: 4px;
border-style: solid;
border-top-color: transparent;
border-bottom-color: transparent;
border-left-color: transparent;
border-right-color: transparent;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.15);
}
.l7-popper.l7-popper-left {
flex-direction: row;
}
.l7-popper.l7-popper-left .l7-popper-arrow {
border-left-color: #fff;
margin: 10px 0;
}
.l7-popper.l7-popper-right {
flex-direction: row-reverse;
}
.l7-popper.l7-popper-right .l7-popper-arrow {
border-right-color: #fff;
margin: 10px 0;
}
.l7-popper.l7-popper-top {
flex-direction: column;
}
.l7-popper.l7-popper-top .l7-popper-arrow {
border-top-color: #fff;
margin: 0 10px;
}
.l7-popper.l7-popper-bottom {
flex-direction: column-reverse;
}
.l7-popper.l7-popper-bottom .l7-popper-arrow {
border-bottom-color: #fff;
margin: 0 10px;
}
.l7-popper.l7-popper-start {
align-items: flex-start;
}
.l7-popper.l7-popper-end {
align-items: flex-end;
}
.l7-select-control--normal {
padding: 4px 0;
}
.l7-select-control--normal .l7-select-control-item {
height: 24px;
line-height: 24px;
display: flex;
align-items: center;
padding: 0 16px;
font-size: 12px;
}
.l7-select-control--normal .l7-select-control-item > * + * {
margin-left: 6px;
}
.l7-select-control--normal .l7-select-control-item input[type='checkbox'] {
height: 14px;
width: 14px;
}
.l7-select-control--normal .l7-select-control-item:hover {
background-color: #f3f3f3;
}
.l7-select-control--image {
padding: 12px 12px 0 12px;
width: 474px;
height: 320px;
overflow: auto;
display: flex;
flex-wrap: wrap;
box-sizing: border-box;
align-items: flex-start;
}
.l7-select-control--image .l7-select-control-item {
margin-right: 12px;
border-radius: 2px;
overflow: hidden;
border: 1px solid #fff;
box-sizing: content-box;
width: calc((100% - 36px) / 3);
display: flex;
flex-direction: column;
justify-content: center;
margin-bottom: 12px;
position: relative;
font-size: 12px;
}
.l7-select-control--image .l7-select-control-item img {
width: 142px;
height: 80px;
}
.l7-select-control--image .l7-select-control-item input[type='checkbox'] {
position: absolute;
right: 0;
top: 0;
}
.l7-select-control--image .l7-select-control-item .l7-select-control-item-row {
display: flex;
justify-content: center;
align-items: center;
line-height: 26px;
}
.l7-select-control--image .l7-select-control-item .l7-select-control-item-row > * + * {
margin-left: 8px;
}
.l7-select-control--image .l7-select-control-item.l7-select-control-item-active {
border-color: #0370fe;
}
.l7-select-control--image .l7-select-control-item:nth-child(3n) {
margin-right: 0;
}
.l7-select-control-item {
cursor: pointer;
}
.l7-select-control-item input[type='checkbox'] {
margin: 0;
cursor: pointer;
}
.l7-select-control--multiple .l7-select-control-item:hover {
background-color: transparent;
}
.l7-control-logo {
width: 89px;
height: 16px;
user-select: none;
}
.l7-control-logo img {
height: 100%;
width: 100%;
}
.l7-control-logo .l7-control-logo-link {
display: block;
cursor: pointer;
}
.l7-control-logo .l7-control-logo-link img {
cursor: pointer;
}
.l7-control-mouse-location {
background-color: #fff;
border-radius: 2px;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.15);
padding: 2px 4px;
min-width: 130px;
}
.l7-control-zoom {
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.15);
border-radius: 2px;
overflow: hidden;
}
.l7-control-zoom .l7-button-control {
box-shadow: 0 0 0;
border-radius: 0;
font-size: 16px;
}
.l7-control-zoom .l7-button-control .l7-iconfont {
width: 14px;
height: 14px;
}
.l7-control-zoom .l7-button-control:first-child {
border-bottom: 1px solid #f0f0f0;
}
.l7-control-scale {
display: flex;
flex-direction: column;
}
.l7-control-scale .l7-control-scale-line {
box-sizing: border-box;
padding: 2px 5px 1px;
overflow: hidden;
color: #595959;
font-size: 10px;
line-height: 1.1;
white-space: nowrap;
background: #fff;
border: 2px solid #000;
border-top: 0;
transition: width 0.1s;
}
.l7-control-scale .l7-control-scale-line + .l7-control-scale .l7-control-scale-line {
margin-top: -2px;
border-top: 2px solid #777;
border-bottom: none;
}
.l7-right .l7-control-scale {
display: flex;
align-items: flex-end;
}
.l7-right .l7-control-scale .l7-control-scale-line {
text-align: right;
}
.l7-popup {
position: absolute;
top: 0;
left: 0;
z-index: 5;
display: -webkit-flex;
display: flex;
pointer-events: none;
will-change: transform;
}
.l7-popup.l7-popup-hide {
display: none;
}
.l7-popup .l7-popup-content {
position: relative;
padding: 16px;
background: #fff;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
pointer-events: auto;
font-size: 14px;
}
.l7-popup .l7-popup-content .l7-popup-content__title {
font-weight: bold;
margin-bottom: 8px;
}
.l7-popup .l7-popup-content .l7-popup-close-button {
position: absolute;
top: 0;
right: 0;
width: 18px;
height: 18px;
line-height: 18px;
text-align: center;
padding: 0;
background-color: transparent;
border: 0;
border-radius: 0 3px 0 0;
cursor: pointer;
font-size: 14px;
}
.l7-popup .l7-popup-tip {
z-index: 1;
width: 0;
height: 0;
border: 10px solid transparent;
}
.l7-popup.l7-popup-anchor-bottom,
.l7-popup.l7-popup-anchor-bottom-left,
.l7-popup.l7-popup-anchor-bottom-right {
-webkit-flex-direction: column-reverse;
flex-direction: column-reverse;
}
.l7-popup.l7-popup-anchor-top,
.l7-popup.l7-popup-anchor-top-left,
.l7-popup.l7-popup-anchor-top-right {
-webkit-flex-direction: column;
flex-direction: column;
}
.l7-popup.l7-popup-anchor-left {
-webkit-flex-direction: row;
flex-direction: row;
}
.l7-popup.l7-popup-anchor-right {
-webkit-flex-direction: row-reverse;
flex-direction: row-reverse;
}
.l7-popup-anchor-top .l7-popup-tip {
-webkit-align-self: center;
align-self: center;
border-top: none;
border-bottom-color: #fff;
}
.l7-popup-anchor-top-left .l7-popup-tip {
-webkit-align-self: flex-start;
align-self: flex-start;
border-top: none;
border-bottom-color: #fff;
border-left: none;
}
.l7-popup-anchor-top-right .l7-popup-tip {
-webkit-align-self: flex-end;
align-self: flex-end;
border-top: none;
border-right: none;
border-bottom-color: #fff;
}
.l7-popup-anchor-bottom .l7-popup-tip {
-webkit-align-self: center;
align-self: center;
border-top-color: #fff;
border-bottom: none;
}
.l7-popup-anchor-bottom-left .l7-popup-tip {
-webkit-align-self: flex-start;
align-self: flex-start;
border-top-color: #fff;
border-bottom: none;
border-left: none;
}
.l7-popup-anchor-bottom-right .l7-popup-tip {
-webkit-align-self: flex-end;
align-self: flex-end;
border-top-color: #fff;
border-right: none;
border-bottom: none;
}
.l7-popup-anchor-left .l7-popup-tip {
-webkit-align-self: center;
align-self: center;
border-right-color: #fff;
border-left: none;
}
.l7-popup-anchor-right .l7-popup-tip {
-webkit-align-self: center;
align-self: center;
border-right: none;
border-left-color: #fff;
}
.l7-popup-anchor-top-left .l7-popup-content {
border-top-left-radius: 0;
}
.l7-popup-anchor-top-right .l7-popup-content {
border-top-right-radius: 0;
}
.l7-popup-anchor-bottom-left .l7-popup-content {
border-bottom-left-radius: 0;
}
.l7-popup-anchor-bottom-right .l7-popup-content {
border-bottom-right-radius: 0;
}
.l7-popup-track-pointer {
display: none;
}
.l7-popup-track-pointer * {
user-select: none;
pointer-events: none;
}
.l7-map:hover .l7-popup-track-pointer {
display: flex;
}
.l7-map:active .l7-popup-track-pointer {
display: none;
}
.l7-layer-popup__row {
font-size: 12px;
}
.l7-layer-popup__row + .l7-layer-popup__row {
margin-top: 4px;
}

View File

@ -0,0 +1,12 @@
@import 'variables';
@import 'l7';
@import 'control';
@import 'button';
@import 'popper';
@import 'select';
@import 'logo';
@import 'mouseLocation';
@import 'zoom';
@import 'scale';
@import 'popup';
@import 'layerPopup';

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,60 @@
.l7-marker-container {
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
}
.l7-marker {
position: absolute !important;
top: 0;
left: 0;
z-index: 5;
cursor: pointer;
}
.l7-marker-cluster {
width: 40px;
height: 40px;
background-color: rgba(181, 226, 140, 0.6);
background-clip: padding-box;
border-radius: 20px;
}
.l7-marker-cluster div {
width: 30px;
height: 30px;
margin-top: 5px;
margin-left: 5px;
font: 12px 'Helvetica Neue', Arial, Helvetica, sans-serif;
text-align: center;
background-color: rgba(110, 204, 57, 0.6);
border-radius: 15px;
}
.l7-marker-cluster span {
line-height: 30px;
}
.l7-touch .l7-control-attribution,
.l7-touch .l7-control-layers,
.l7-touch .l7-bar {
box-shadow: none;
}
.l7-touch .l7-control-layers,
.l7-touch .l7-bar {
background-clip: padding-box;
border: 2px solid rgba(0, 0, 0, 0.2);
}
// 隐藏底图 Logo
.mapboxgl-ctrl-logo,
.amap-logo {
display: none !important;
}
.l7-select-box {
border: 3px dashed gray;
border-radius: 2px;
position: absolute;
z-index: 1000;
box-sizing: border-box;
}

View File

@ -0,0 +1,8 @@
@import 'variables';
.l7-layer-popup__row {
font-size: 12px;
& + & {
margin-top: 4px;
}
}

View File

@ -0,0 +1,18 @@
@import 'variables';
.l7-control-logo {
width: 89px;
height: 16px;
user-select: none;
img {
height: 100%;
width: 100%;
}
.l7-control-logo-link {
display: block;
cursor: pointer;
img {
cursor: pointer;
}
}
}

View File

@ -0,0 +1,9 @@
@import 'variables';
.l7-control-mouse-location {
background-color: @l7-control-bg-color;
border-radius: @l7-btn-control-border-radius;
box-shadow: @l7-control-shadow;
padding: 2px 4px;
min-width: 130px;
}

View File

@ -0,0 +1,64 @@
@import 'variables';
.l7-popper {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
z-index: 5;
color: @l7-control-font-color;
&.l7-popper-hide {
display: none;
}
.l7-popper-content {
min-height: @l7-btn-control-size;
background: @l7-popper-control-bg-color;
border-radius: @l7-btn-control-border-radius;
box-shadow: @l7-control-shadow;
}
.l7-popper-arrow {
width: 0;
height: 0;
border-width: @l7-popper-control-arrow-size;
border-style: solid;
border-top-color: transparent;
border-bottom-color: transparent;
border-left-color: transparent;
border-right-color: transparent;
box-shadow: @l7-control-shadow;
}
&.l7-popper-left {
flex-direction: row;
.l7-popper-arrow {
border-left-color: @l7-popper-control-bg-color;
margin: (@l7-btn-control-size / 2 - @l7-popper-control-arrow-size) 0;
}
}
&.l7-popper-right {
flex-direction: row-reverse;
.l7-popper-arrow {
border-right-color: @l7-popper-control-bg-color;
margin: (@l7-btn-control-size / 2 - @l7-popper-control-arrow-size) 0;
}
}
&.l7-popper-top {
flex-direction: column;
.l7-popper-arrow {
border-top-color: @l7-popper-control-bg-color;
margin: 0 (@l7-btn-control-size / 2 - @l7-popper-control-arrow-size);
}
}
&.l7-popper-bottom {
flex-direction: column-reverse;
.l7-popper-arrow {
border-bottom-color: @l7-popper-control-bg-color;
margin: 0 (@l7-btn-control-size / 2 - @l7-popper-control-arrow-size);
}
}
&.l7-popper-start {
align-items: flex-start;
}
&.l7-popper-end {
align-items: flex-end;
}
}

View File

@ -0,0 +1,169 @@
@import 'variables';
.l7-popup {
position: absolute;
top: 0;
left: 0;
z-index: 5;
display: -webkit-flex;
display: flex;
pointer-events: none;
will-change: transform;
&.l7-popup-hide {
display: none;
}
.l7-popup-content {
position: relative;
padding: 16px;
background: #fff;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
pointer-events: auto;
font-size: 14px;
.l7-popup-content__title {
font-weight: bold;
margin-bottom: 8px;
}
.l7-popup-close-button {
position: absolute;
top: 0;
right: 0;
width: 18px;
height: 18px;
line-height: 18px;
text-align: center;
padding: 0;
background-color: transparent;
border: 0;
border-radius: 0 3px 0 0;
cursor: pointer;
font-size: 14px;
}
}
.l7-popup-tip {
z-index: 1;
width: 0;
height: 0;
border: 10px solid transparent;
}
&.l7-popup-anchor-bottom,
&.l7-popup-anchor-bottom-left,
&.l7-popup-anchor-bottom-right {
-webkit-flex-direction: column-reverse;
flex-direction: column-reverse;
}
&.l7-popup-anchor-top,
&.l7-popup-anchor-top-left,
&.l7-popup-anchor-top-right {
-webkit-flex-direction: column;
flex-direction: column;
}
&.l7-popup-anchor-left {
-webkit-flex-direction: row;
flex-direction: row;
}
&.l7-popup-anchor-right {
-webkit-flex-direction: row-reverse;
flex-direction: row-reverse;
}
}
.l7-popup-anchor-top .l7-popup-tip {
-webkit-align-self: center;
align-self: center;
border-top: none;
border-bottom-color: #fff;
}
.l7-popup-anchor-top-left .l7-popup-tip {
-webkit-align-self: flex-start;
align-self: flex-start;
border-top: none;
border-bottom-color: #fff;
border-left: none;
}
.l7-popup-anchor-top-right .l7-popup-tip {
-webkit-align-self: flex-end;
align-self: flex-end;
border-top: none;
border-right: none;
border-bottom-color: #fff;
}
.l7-popup-anchor-bottom .l7-popup-tip {
-webkit-align-self: center;
align-self: center;
border-top-color: #fff;
border-bottom: none;
}
.l7-popup-anchor-bottom-left .l7-popup-tip {
-webkit-align-self: flex-start;
align-self: flex-start;
border-top-color: #fff;
border-bottom: none;
border-left: none;
}
.l7-popup-anchor-bottom-right .l7-popup-tip {
-webkit-align-self: flex-end;
align-self: flex-end;
border-top-color: #fff;
border-right: none;
border-bottom: none;
}
.l7-popup-anchor-left .l7-popup-tip {
-webkit-align-self: center;
align-self: center;
border-right-color: #fff;
border-left: none;
}
.l7-popup-anchor-right .l7-popup-tip {
-webkit-align-self: center;
align-self: center;
border-right: none;
border-left-color: #fff;
}
.l7-popup-anchor-top-left .l7-popup-content {
border-top-left-radius: 0;
}
.l7-popup-anchor-top-right .l7-popup-content {
border-top-right-radius: 0;
}
.l7-popup-anchor-bottom-left .l7-popup-content {
border-bottom-left-radius: 0;
}
.l7-popup-anchor-bottom-right .l7-popup-content {
border-bottom-right-radius: 0;
}
.l7-popup-track-pointer {
display: none;
* {
user-select: none;
pointer-events: none;
}
}
.l7-map:hover .l7-popup-track-pointer {
display: flex;
}
.l7-map:active .l7-popup-track-pointer {
display: none;
}

View File

@ -0,0 +1,34 @@
@import 'variables';
.l7-control-scale {
display: flex;
flex-direction: column;
.l7-control-scale-line {
box-sizing: border-box;
padding: 2px 5px 1px;
overflow: hidden;
color: @l7-control-font-color;
font-size: 10px;
line-height: 1.1;
white-space: nowrap;
background: @l7-control-bg-color;
border: 2px solid #000;
border-top: 0;
transition: width 0.1s;
& + & {
margin-top: -2px;
border-top: 2px solid #777;
border-bottom: none;
}
}
}
.l7-right {
.l7-control-scale {
display: flex;
align-items: flex-end;
.l7-control-scale-line {
text-align: right;
}
}
}

View File

@ -0,0 +1,86 @@
@import 'variables';
.l7-select-control--normal {
padding: 4px 0;
.l7-select-control-item {
height: 24px;
line-height: 24px;
display: flex;
align-items: center;
padding: 0 16px;
font-size: 12px;
> * + * {
margin-left: 6px;
}
input[type='checkbox'] {
height: 14px;
width: 14px;
}
&:hover {
background-color: @l7-btn-control-bg-hover-color;
}
}
}
.l7-select-control--image {
padding: 12px 12px 0 12px;
width: @l7-select-control-image-popper-width;
height: 320px;
overflow: auto;
display: flex;
flex-wrap: wrap;
box-sizing: border-box;
align-items: flex-start;
.l7-select-control-item {
margin-right: 12px;
border-radius: @l7-btn-control-border-radius;
overflow: hidden;
border: 1px solid @l7-popper-control-bg-color;
box-sizing: content-box;
width: calc((100% - 36px) / 3);
display: flex;
flex-direction: column;
justify-content: center;
margin-bottom: 12px;
position: relative;
font-size: 12px;
img {
width: 142px;
height: 80px;
}
input[type='checkbox'] {
position: absolute;
right: 0;
top: 0;
}
.l7-select-control-item-row {
display: flex;
justify-content: center;
align-items: center;
line-height: 26px;
> * + * {
margin-left: 8px;
}
}
&.l7-select-control-item-active {
border-color: @l7-select-control-active-color;
}
&:nth-child(3n) {
margin-right: 0;
}
}
}
.l7-select-control-item {
cursor: pointer;
input[type='checkbox'] {
margin: 0;
cursor: pointer;
}
}
.l7-select-control--multiple {
.l7-select-control-item:hover {
background-color: transparent;
}
}

View File

@ -0,0 +1,28 @@
// Control
@l7-control-space: 8px;
@l7-control-font-size: 12px;
@l7-control-font-color: #595959;
@l7-control-bg-color: #fff;
@l7-control-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.15);
// ButtonControl
@l7-btn-control-bg-color: @l7-control-bg-color;
@l7-btn-control-bg-hover-color: #f3f3f3;
@l7-btn-control-bg-active-color: @l7-btn-control-bg-hover-color;
@l7-btn-control-size: 28px;
@l7-btn-icon-size: 16px;
@l7-btn-control-border-radius: 2px;
@l7-btn-control-disabled-bg-color: #fafafa;
@l7-btn-control-disabled-font-color: #bdbdbd;
@l7-btn-border-color: #f0f0f0;
@l7-btn-column-height: 44px;
// PopperControl
@l7-popper-control-bg-color: @l7-btn-control-bg-color;
@l7-popper-control-arrow-size: 4px;
// SelectControl
@l7-select-control-active-color: #0370fe;
@l7-select-control-image-popper-width: 474px;
@position-list: top, right, bottom, left;

View File

@ -0,0 +1,21 @@
@import 'variables';
@zoom-icon-size: 14px;
.l7-control-zoom {
box-shadow: @l7-control-shadow;
border-radius: @l7-btn-control-border-radius;
overflow: hidden;
.l7-button-control {
box-shadow: 0 0 0;
border-radius: 0;
font-size: @l7-btn-icon-size;
.l7-iconfont {
width: @zoom-icon-size;
height: @zoom-icon-size;
}
&:first-child {
border-bottom: 1px solid @l7-btn-border-color;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with Vectornator (http://vectornator.io/) -->
<svg height="100%" stroke-miterlimit="10" style="fill-rule:nonzero;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;" version="1.1" viewBox="0 0 682.67 682.67" width="100%" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:vectornator="http://vectornator.io" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs/>
<clipPath id="ArtboardFrame">
<rect height="682.67" width="682.67" x="0" y="0"/>
</clipPath>
<g clip-path="url(#ArtboardFrame)" id="Untitled" vectornator:layerName="Untitled">
<path d="M0 477.87L0 580.27C0.00552233 636.822 45.8483 682.664 102.4 682.67L204.8 682.67C223.424 682.351 238.356 667.162 238.356 648.535C238.356 629.908 223.424 614.719 204.8 614.4L102.4 614.4C83.5505 614.4 68.27 599.119 68.27 580.27L68.27 477.87C68.4812 465.535 62.0216 454.046 51.3734 447.817C40.7252 441.588 27.5448 441.588 16.8966 447.817C6.24842 454.046-0.211108 465.535 0 477.87ZM477.87 682.67L580.27 682.67C636.822 682.664 682.664 636.822 682.67 580.27L682.67 477.87C682.881 465.535 676.422 454.046 665.773 447.817C655.125 441.588 641.945 441.588 631.297 447.817C620.648 454.046 614.189 465.535 614.4 477.87L614.4 580.27C614.4 599.119 599.119 614.4 580.27 614.4L477.87 614.4C465.535 614.189 454.046 620.648 447.817 631.297C441.588 641.945 441.588 655.125 447.817 665.773C454.046 676.422 465.535 682.881 477.87 682.67ZM682.67 204.8L682.67 102.4C682.664 45.8483 636.822 0.00553344 580.27 1.15748e-06L477.87 1.15748e-06C465.535-0.211139 454.046 6.24838 447.817 16.8966C441.588 27.5448 441.588 40.7252 447.817 51.3734C454.046 62.0216 465.535 68.4811 477.87 68.27L580.27 68.27C599.119 68.27 614.4 83.5505 614.4 102.4L614.4 204.8C614.719 223.424 629.908 238.356 648.535 238.356C667.162 238.356 682.351 223.424 682.67 204.8ZM204.8 0L102.4 0C45.8483 0.00551165 0.00552271 45.8483-3.66902e-07 102.4L-3.66902e-07 204.8C0.318807 223.424 15.5078 238.356 34.135 238.356C52.7622 238.356 67.9512 223.424 68.27 204.8L68.27 102.4C68.27 83.5505 83.5505 68.27 102.4 68.27L204.8 68.27C223.424 67.9512 238.356 52.7622 238.356 34.135C238.356 15.5078 223.424 0.318804 204.8 0Z" fill="#333333" fill-rule="nonzero" opacity="1" stroke="none"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,16 +1,23 @@
import Control from './control/BaseControl';
import Layers from './control/layer';
import Logo from './control/logo';
import Scale from './control/scale';
import Zoom from './control/zoom';
import Marker from './marker';
import MarkerLayer from './marker-layer';
import Popup from './popup';
import './assets/iconfont/iconfont.js';
// 引入样式
// TODO: 使用 Less 或者 Sass每个组件单独引用自身样式
import './css/l7.css';
import './css/index.css';
export { Control, Logo, Scale, Zoom, Layers, Marker, Popup, MarkerLayer };
export * from './control/baseControl';
export * from './control/logo';
export * from './control/fullscreen';
export * from './control/exportImage';
export * from './control/geoLocate';
export * from './control/mapTheme';
export * from './control/layerControl';
export * from './control/mouseLocation';
export * from './control/zoom';
export * from './control/scale';
export * from './popup/popup';
export * from './popup/layerPopup';
export { Marker, MarkerLayer };
export * from './interface';

View File

@ -1,27 +1,4 @@
import { IControlOption } from '@antv/l7-core';
export interface ILayerControlOption extends IControlOption {
collapsed: boolean;
autoZIndex: boolean;
hideSingleBase: boolean;
sortLayers: boolean;
sortFunction: (...args: any[]) => any;
}
export interface IScaleControlOption extends IControlOption {
maxWidth: number;
metric: boolean;
updateWhenIdle: boolean;
imperial: boolean;
}
export interface IZoomControlOption extends IControlOption {
zoomInText: string;
zoomInTitle: string;
zoomOutText: string;
zoomOutTitle: string;
}
export type ControlEvent = 'show' | 'hide' | 'add' | 'remove' | string;
export interface IMarkerStyleOption {
element?: (...args: any[]) => any;

View File

@ -1,262 +0,0 @@
import {
ILngLat,
IMapService,
IPopup,
IPopupOption,
ISceneService,
TYPES,
} from '@antv/l7-core';
import {
anchorTranslate,
anchorType,
applyAnchorClass,
bindAll,
DOM,
} from '@antv/l7-utils';
import { EventEmitter } from 'eventemitter3';
import { Container } from 'inversify';
/** colse event */
export default class Popup extends EventEmitter implements IPopup {
private popupOption: IPopupOption;
private mapsService: IMapService<unknown>;
private sceneSerive: ISceneService;
private lngLat: ILngLat;
private content: HTMLElement;
private closeButton: HTMLElement;
private timeoutInstance: any;
private container: HTMLElement;
private tip: HTMLElement;
private scene: Container;
constructor(cfg?: Partial<IPopupOption>) {
super();
this.popupOption = {
...this.getdefault(),
...cfg,
};
bindAll(['update', 'onClickClose', 'remove'], this);
}
public addTo(scene: Container) {
this.mapsService = scene.get<IMapService>(TYPES.IMapService);
this.sceneSerive = scene.get<ISceneService>(TYPES.ISceneService);
this.mapsService.on('camerachange', this.update);
this.mapsService.on('viewchange', this.update);
this.scene = scene;
this.update();
if (this.popupOption.closeOnClick) {
this.timeoutInstance = setTimeout(() => {
this.mapsService.on('click', this.onClickClose);
}, 30);
}
this.emit('open');
return this;
}
public close(): void {
this.remove();
}
public open(): void {
this.addTo(this.scene);
}
public setHTML(html: string) {
const frag = window.document.createDocumentFragment();
const temp = window.document.createElement('body');
let child: ChildNode | null;
temp.innerHTML = html;
while (true) {
child = temp.firstChild;
if (!child) {
break;
}
frag.appendChild(child);
}
return this.setDOMContent(frag);
}
public setLnglat(lngLat: ILngLat | number[]): this {
this.lngLat = lngLat as ILngLat;
if (Array.isArray(lngLat)) {
this.lngLat = {
lng: lngLat[0],
lat: lngLat[1],
};
}
if (this.mapsService) {
this.mapsService.on('camerachange', this.update);
this.mapsService.on('viewchange', this.update);
}
this.update();
return this;
}
public getLnglat(): ILngLat {
return this.lngLat;
}
public setText(text: string) {
return this.setDOMContent(window.document.createTextNode(text));
}
public setMaxWidth(maxWidth: string): this {
this.popupOption.maxWidth = maxWidth;
this.update();
return this;
}
public setDOMContent(htmlNode: ChildNode | DocumentFragment) {
this.createContent();
this.content.appendChild(htmlNode);
this.update();
return this;
}
// 移除popup
public remove() {
if (this.content) {
this.removeDom(this.content);
}
if (this.container) {
this.removeDom(this.container);
// @ts-ignore
delete this.container;
}
if (this.mapsService) {
// TODO: mapbox AMap 事件同步
this.mapsService.off('camerachange', this.update);
this.mapsService.off('viewchange', this.update);
this.mapsService.off('click', this.onClickClose);
// @ts-ignore
delete this.mapsService;
}
clearTimeout(this.timeoutInstance);
this.emit('close');
return this;
}
public isOpen() {
return !!this.mapsService;
}
private createContent() {
if (this.content) {
DOM.remove(this.content);
}
this.content = DOM.create('div', 'l7-popup-content', this.container);
if (this.popupOption.closeButton) {
this.closeButton = DOM.create(
'button',
'l7-popup-close-button',
this.content,
);
if (this.popupOption.closeButtonOffsets) {
// 关闭按钮的偏移
this.closeButton.style.right =
this.popupOption.closeButtonOffsets[0] + 'px';
this.closeButton.style.top =
this.popupOption.closeButtonOffsets[1] + 'px';
}
// this.closeButton.type = 'button';
this.closeButton.setAttribute('aria-label', 'Close popup');
this.closeButton.innerHTML = '&#215;';
this.closeButton.addEventListener('click', this.onClickClose);
}
}
private creatDom(tagName: string, className: string, container: HTMLElement) {
const el = window.document.createElement(tagName);
if (className !== undefined) {
el.className = className;
}
if (container) {
container.appendChild(el);
}
return el;
}
private removeDom(node: ChildNode) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
}
private getdefault() {
return {
closeButton: true,
closeOnClick: true,
maxWidth: '240px',
offsets: [0, 0],
anchor: anchorType.BOTTOM,
className: '',
stopPropagation: true,
};
}
private onClickClose(e: Event) {
if (e.stopPropagation) {
e.stopPropagation();
}
this.remove();
}
private update() {
const hasPosition = this.lngLat;
const { className, maxWidth, anchor } = this.popupOption;
if (!this.mapsService || !hasPosition || !this.content) {
return;
}
const popupContainer = this.mapsService.getMarkerContainer();
if (!this.container && popupContainer) {
this.container = this.creatDom(
'div',
'l7-popup',
popupContainer as HTMLElement,
);
this.tip = this.creatDom('div', 'l7-popup-tip', this.container);
this.container.appendChild(this.content);
if (className) {
className
.split(' ')
.forEach((name) => this.container.classList.add(name));
}
// 高德地图需要阻止事件冒泡 // 测试mapbox 地图不需要添加
const { stopPropagation } = this.popupOption;
if (stopPropagation) {
['mousemove', 'mousedown', 'mouseup', 'click', 'dblclick'].forEach(
(type) => {
this.container.addEventListener(type, (e) => {
e.stopPropagation();
});
},
);
}
this.container.style.whiteSpace = 'nowrap';
}
if (maxWidth && this.container.style.maxWidth !== maxWidth) {
this.container.style.maxWidth = maxWidth;
}
this.updatePosition();
DOM.setTransform(this.container, `${anchorTranslate[anchor]}`);
applyAnchorClass(this.container, anchor, 'popup');
}
private updatePosition() {
if (!this.mapsService) {
return;
}
const { lng, lat } = this.lngLat;
const { offsets } = this.popupOption;
const pos = this.mapsService.lngLatToContainer([lng, lat]);
this.container.style.left = pos.x + offsets[0] + 'px';
this.container.style.top = pos.y - offsets[1] + 'px';
}
}

View File

@ -0,0 +1,269 @@
import { ILayer, IPopupOption } from '@antv/l7-core';
// @ts-ignore
// tslint:disable-next-line:no-implicit-dependencies
import { BaseLayer } from '@antv/l7-layers';
import { DOM } from '@antv/l7-utils';
import { Container } from 'inversify';
import { get } from 'lodash';
// import { Container } from 'inversify';
import Popup from './popup';
export type LayerField = {
field: string;
formatField?: (field: string) => string;
formatValue?: (value: any) => any;
getValue?: (feature: any) => any;
};
export type LayerPopupConfigItem = {
layer: BaseLayer | string;
fields: Array<LayerField | string>;
};
export interface ILayerPopupOption extends IPopupOption {
config: LayerPopupConfigItem[];
trigger: 'hover' | 'click';
}
type LayerMapInfo = {
onMouseMove?: (layer: BaseLayer, e: any) => void;
onMouseOut?: (layer: BaseLayer, e: any) => void;
onClick?: (layer: BaseLayer, e: any) => void;
onSourceUpdate?: (layer: BaseLayer) => void;
} & Partial<LayerPopupConfigItem>;
export { LayerPopup };
export default class LayerPopup extends Popup<ILayerPopupOption> {
/**
*
* @protected
*/
protected layerConfigMap: WeakMap<ILayer, LayerMapInfo> = new WeakMap();
/**
* id
* @protected
*/
protected displayFeatureInfo?: {
layer: ILayer;
featureId: number;
};
public addTo(scene: Container) {
super.addTo(scene);
this.bindLayerEvent();
this.hide();
return this;
}
public remove() {
super.remove();
this.unbindLayerEvent();
return this;
}
public setOptions(option: Partial<ILayerPopupOption>) {
this.unbindLayerEvent();
super.setOptions(option);
this.bindLayerEvent();
return this;
}
protected getDefault(option: Partial<ILayerPopupOption>): ILayerPopupOption {
const isClickTrigger = option.trigger === 'click';
return {
...super.getDefault(option),
trigger: 'hover',
followCursor: !isClickTrigger,
lngLat: {
lng: 0,
lat: 0,
},
offsets: [0, 10],
closeButton: false,
closeOnClick: false,
autoClose: false,
closeOnEsc: false,
};
}
/**
*
* @protected
*/
protected bindLayerEvent() {
const { config, trigger } = this.popupOption;
config.forEach((configItem) => {
const layer = this.getLayerByConfig(configItem);
if (!layer) {
return;
}
const layerInfo: LayerMapInfo = {
...configItem,
};
if (trigger === 'hover') {
const onMouseMove = this.onLayerMouseMove.bind(this, layer);
const onMouseOut = this.onLayerMouseOut.bind(this, layer);
layerInfo.onMouseMove = onMouseMove;
layerInfo.onMouseOut = onMouseOut;
layer?.on('mousemove', onMouseMove);
layer?.on('mouseout', onMouseOut);
} else {
const onClick = this.onLayerClick.bind(this, layer);
layerInfo.onClick = onClick;
layer?.on('click', onClick);
}
const source = layer.getSource();
const onSourceUpdate = this.onSourceUpdate.bind(this, layer);
source?.on('update', onSourceUpdate);
layerInfo.onSourceUpdate = onSourceUpdate;
this.layerConfigMap.set(layer, layerInfo);
});
}
/**
*
* @protected
*/
protected unbindLayerEvent() {
const { config } = this.popupOption;
config.forEach((configItem) => {
const layer = this.getLayerByConfig(configItem);
const layerInfo = layer && this.layerConfigMap.get(layer);
if (!layerInfo) {
return;
}
const { onMouseMove, onMouseOut, onClick, onSourceUpdate } = layerInfo;
if (onMouseMove) {
layer.off('mousemove', onMouseMove);
}
if (onMouseOut) {
layer.off('mouseout', onMouseOut);
}
if (onClick) {
layer.off('click', onClick);
}
if (onSourceUpdate) {
layer?.getSource()?.off('update', onSourceUpdate);
}
});
}
protected onLayerMouseMove(layer: ILayer, e: any) {
if (!this.isSameFeature(layer, e.featureId)) {
const frag = this.getLayerInfoFrag(layer, e);
this.setDOMContent(frag);
this.displayFeatureInfo = {
layer,
featureId: e.featureId,
};
}
if (!this.isShow) {
this.show();
}
}
protected onLayerMouseOut(layer: ILayer, e: any) {
this.displayFeatureInfo = undefined;
if (this.isShow) {
this.hide();
}
}
protected onLayerClick(layer: ILayer, e: any) {
if (this.isShow && this.isSameFeature(layer, e.featureId)) {
this.hide();
} else {
const frag = this.getLayerInfoFrag(layer, e);
this.setDOMContent(frag);
this.setLnglat(e.lngLat);
this.show();
this.displayFeatureInfo = {
layer,
featureId: e.featureId,
};
}
}
protected onSourceUpdate(layer: ILayer) {
if (this.displayFeatureInfo?.layer === layer) {
this.hide();
this.displayFeatureInfo = undefined;
}
}
/**
* HTML
* @param layer
* @param e
* @protected
*/
protected getLayerInfoFrag(layer: ILayer, e: any): DocumentFragment {
const layerInfo = this.layerConfigMap.get(layer);
const frag = document.createDocumentFragment();
if (layerInfo) {
let feature = e.feature;
if (
feature.type === 'Feature' &&
'properties' in feature &&
'geometry' in feature
) {
feature = feature.properties;
}
const { fields } = layerInfo;
fields?.forEach((fieldConfig) => {
const { field, formatField, formatValue, getValue } =
typeof fieldConfig === 'string'
? ({ field: fieldConfig } as any)
: fieldConfig;
const row = DOM.create('div', 'l7-layer-popup__row');
const value = getValue ? getValue(e.feature) : get(feature, field);
row.innerHTML = `${formatField ? formatField(field) : field}: ${
formatValue ? formatValue(value) : value
}`;
frag.appendChild(row);
});
}
return frag;
}
/**
* Layer 访 Layer
* @param config
* @protected
*/
protected getLayerByConfig(config: LayerPopupConfigItem): ILayer | undefined {
const layer = config.layer;
if (layer instanceof Object) {
return layer;
}
if (typeof layer === 'string') {
return (
this.layerService.getLayer(layer) ||
this.layerService.getLayerByName(layer)
);
}
}
/**
* Feature
* @param layer
* @param featureId
* @protected
*/
protected isSameFeature(layer: ILayer, featureId: number) {
const displayFeatureInfo = this.displayFeatureInfo;
return (
displayFeatureInfo &&
layer === displayFeatureInfo.layer &&
featureId === displayFeatureInfo.featureId
);
}
}

View File

@ -0,0 +1,575 @@
import {
ILayerService,
ILngLat,
IMapService,
IPopup,
IPopupOption,
ISceneService,
PopupHTML,
TYPES,
} from '@antv/l7-core';
import {
anchorTranslate,
anchorType,
applyAnchorClass,
DOM,
} from '@antv/l7-utils';
import { EventEmitter } from 'eventemitter3';
import { Container } from 'inversify';
import { createL7Icon } from '../utils/icon';
export { Popup };
export default class Popup<O extends IPopupOption = IPopupOption>
extends EventEmitter
implements IPopup {
/**
*
* @protected
*/
protected popupOption: O;
protected mapsService: IMapService;
protected sceneService: ISceneService;
protected layerService: ILayerService;
protected scene: Container;
/**
* DOM
* @protected
*/
protected closeButton?: HTMLElement | SVGElement;
/**
* Popup DOM content tip
* @protected
*/
protected container: HTMLElement;
/**
* popup
* @protected
*/
protected content: HTMLElement;
/**
* popup
* @protected
*/
protected contentTitle?: HTMLElement;
/**
* popup
* @protected
*/
protected contentPanel: HTMLElement;
/**
* popup
* @protected
*/
protected title: HTMLElement;
/**
* DOM
* @protected
*/
protected tip: HTMLElement;
/**
*
* @protected
*/
protected isShow: boolean = true;
protected get lngLat() {
return (
this.popupOption.lngLat ?? {
lng: 0,
lat: 0,
}
);
}
protected set lngLat(newLngLat: ILngLat) {
this.popupOption.lngLat = newLngLat;
}
constructor(cfg?: Partial<O>) {
super();
this.popupOption = {
...this.getDefault(cfg ?? {}),
...cfg,
};
const { lngLat } = this.popupOption;
if (lngLat) {
this.lngLat = lngLat;
}
}
public getIsShow() {
return this.isShow;
}
public addTo(scene: Container) {
this.mapsService = scene.get<IMapService>(TYPES.IMapService);
this.sceneService = scene.get<ISceneService>(TYPES.ISceneService);
this.layerService = scene.get<ILayerService>(TYPES.ILayerService);
this.mapsService.on('camerachange', this.update);
this.mapsService.on('viewchange', this.update);
this.scene = scene;
this.update();
this.updateCloseOnClick();
this.updateCloseOnEsc();
this.updateFollowCursor();
const { html, text } = this.popupOption;
if (html) {
this.setHTML(html);
} else if (text) {
this.setText(text);
}
this.emit('open');
return this;
}
// 移除popup
public remove() {
if (!this.isOpen()) {
return;
}
if (this.content) {
DOM.remove(this.content);
}
if (this.container) {
DOM.remove(this.container);
// @ts-ignore
delete this.container;
}
if (this.mapsService) {
// TODO: mapbox AMap 事件同步
this.mapsService.off('camerachange', this.update);
this.mapsService.off('viewchange', this.update);
this.updateCloseOnClick(true);
this.updateCloseOnEsc(true);
this.updateFollowCursor(true);
// @ts-ignore
delete this.mapsService;
}
this.emit('close');
return this;
}
/**
* option
*/
public getOptions() {
return this.popupOption;
}
public setOptions(option: Partial<O>) {
this.popupOption = {
...this.popupOption,
...option,
};
if (
this.checkUpdateOption(option, [
'closeButton',
'closeButtonOffsets',
'maxWidth',
'anchor',
'stopPropagation',
'className',
'style',
'lngLat',
'offsets',
'title',
])
) {
if (this.container) {
DOM.remove(this.container);
// @ts-ignore
this.container = undefined;
}
if (this.popupOption.html) {
this.setHTML(this.popupOption.html);
} else if (this.popupOption.text) {
this.setText(this.popupOption.text);
}
}
if (this.checkUpdateOption(option, ['closeOnEsc'])) {
this.updateCloseOnEsc();
}
if (this.checkUpdateOption(option, ['closeOnClick'])) {
this.updateCloseOnClick();
}
if (this.checkUpdateOption(option, ['followCursor'])) {
this.updateFollowCursor();
}
if (this.checkUpdateOption(option, ['html']) && option.html) {
this.setHTML(option.html);
} else if (this.checkUpdateOption(option, ['text']) && option.text) {
this.setText(option.text);
}
if (this.checkUpdateOption(option, ['lngLat']) && option.lngLat) {
this.setLnglat(option.lngLat);
}
return this;
}
public open() {
this.addTo(this.scene);
return this;
}
public close() {
this.remove();
return this;
}
public show() {
if (this.isShow) {
return;
}
if (this.container) {
DOM.removeClass(this.container, 'l7-popup-hide');
}
this.isShow = true;
this.emit('show');
return this;
}
public hide() {
if (!this.isShow) {
return;
}
if (this.container) {
DOM.addClass(this.container, 'l7-popup-hide');
}
this.isShow = false;
this.emit('hide');
return this;
}
/**
* HTML
* @param html
*/
public setHTML(html: PopupHTML) {
this.popupOption.html = html;
return this.setDOMContent(this.getPopupHTMLFragment(html));
}
/**
* Popup
* @param text
*/
public setText(text: string) {
this.popupOption.text = text;
return this.setDOMContent(window.document.createTextNode(text));
}
/**
*
*/
public panToPopup() {
const { lng, lat } = this.lngLat;
if (this.popupOption.autoPan) {
this.mapsService.panTo([lng, lat]);
}
return this;
}
public setLngLat(lngLat: ILngLat | [number, number]): this {
return this.setLnglat(lngLat);
}
/**
* Popup
* @param lngLat
*/
public setLnglat(lngLat: ILngLat | [number, number]): this {
this.lngLat = lngLat as ILngLat;
if (Array.isArray(lngLat)) {
this.lngLat = {
lng: lngLat[0],
lat: lngLat[1],
};
}
if (this.mapsService) {
// 防止事件重复监听
this.mapsService.off('camerachange', this.update);
this.mapsService.off('viewchange', this.update);
this.mapsService.on('camerachange', this.update);
this.mapsService.on('viewchange', this.update);
}
this.update();
if (this.popupOption.autoPan) {
setTimeout(() => {
this.panToPopup();
}, 0);
}
return this;
}
/**
* Popup
*/
public getLnglat(): ILngLat {
return this.lngLat;
}
/**
* Popup
* @param maxWidth
*/
public setMaxWidth(maxWidth: string): this {
this.popupOption.maxWidth = maxWidth;
this.update();
return this;
}
public isOpen() {
return !!this.mapsService;
}
protected onMouseMove = (e: MouseEvent) => {
const container = this.mapsService.getMapContainer();
const { left = 0, top = 0 } = container?.getBoundingClientRect() ?? {};
this.setPopupPosition(e.clientX - left, e.clientY - top);
};
/**
*
* @protected
*/
protected updateLngLatPosition = () => {
if (!this.mapsService || this.popupOption.followCursor) {
return;
}
const { lng, lat } = this.lngLat;
const { x, y } = this.mapsService.lngLatToContainer([lng, lat]);
this.setPopupPosition(x, y);
};
protected getDefault(option: Partial<O>): O {
// tslint:disable-next-line:no-object-literal-type-assertion
return {
closeButton: true,
closeOnClick: true,
maxWidth: '240px',
offsets: [0, 0],
anchor: anchorType.BOTTOM,
stopPropagation: true,
autoPan: false,
autoClose: true,
closeOnEsc: false,
followCursor: false,
} as O;
}
/**
* Popup HTML
* @param htmlNode
*/
protected setDOMContent(htmlNode: ChildNode | DocumentFragment) {
this.createContent();
this.contentPanel.appendChild(htmlNode);
this.update();
return this;
}
/**
* Popup
* @protected
*/
protected updateCloseOnClick(onlyClear?: boolean) {
this.mapsService.off('click', this.onCloseButtonClick);
if (this.popupOption.closeOnClick && !onlyClear) {
this.mapsService.on('click', this.onCloseButtonClick);
}
}
protected updateCloseOnEsc(onlyClear?: boolean) {
window.removeEventListener('keydown', this.onKeyDown);
if (this.popupOption.closeOnEsc && !onlyClear) {
window.addEventListener('keydown', this.onKeyDown);
}
}
protected updateFollowCursor(onlyClear?: boolean) {
const container = this.mapsService.getContainer()!;
container.removeEventListener('mousemove', this.onMouseMove);
if (this.popupOption.followCursor && !onlyClear) {
container.addEventListener('mousemove', this.onMouseMove);
}
}
protected onKeyDown = (e: KeyboardEvent) => {
if (e.keyCode === 27) {
this.remove();
}
};
/**
* Popup DOM setHTML setText
* @protected
*/
protected createContent() {
if (this.content) {
DOM.remove(this.content);
}
this.content = DOM.create('div', 'l7-popup-content', this.container);
if (this.popupOption.title) {
this.contentTitle = DOM.create(
'div',
'l7-popup-content__title',
this.content,
);
this.contentTitle?.append(
this.getPopupHTMLFragment(this.popupOption.title),
);
} else {
this.contentTitle = undefined;
}
if (this.popupOption.closeButton) {
const closeButton = createL7Icon('l7-icon-guanbi');
DOM.addClass(closeButton, 'l7-popup-close-button');
this.content.appendChild(closeButton);
if (this.popupOption.closeButtonOffsets) {
// 关闭按钮的偏移
closeButton.style.right = this.popupOption.closeButtonOffsets[0] + 'px';
closeButton.style.top = this.popupOption.closeButtonOffsets[1] + 'px';
}
// this.closeButton.type = 'button';
closeButton.setAttribute('aria-label', 'Close popup');
closeButton.addEventListener('click', this.onCloseButtonClick);
this.closeButton = closeButton;
} else {
this.closeButton = undefined;
}
this.contentPanel = DOM.create(
'div',
'l7-popup-content__panel',
this.content,
);
}
protected onCloseButtonClick = (e: Event) => {
if (e.stopPropagation) {
e.stopPropagation();
}
this.remove();
};
protected update = () => {
const hasPosition = !!this.lngLat;
const {
className,
style,
maxWidth,
anchor,
stopPropagation,
} = this.popupOption;
if (!this.mapsService || !hasPosition || !this.content) {
return;
}
const popupContainer = this.mapsService.getMarkerContainer();
// 如果当前没有创建 Popup 容器则创建
if (!this.container && popupContainer) {
this.container = DOM.create(
'div',
`l7-popup ${className ?? ''} ${!this.isShow ? 'l7-popup-hide' : ''}`,
popupContainer as HTMLElement,
);
if (style) {
this.container.setAttribute('style', style);
}
this.tip = DOM.create('div', 'l7-popup-tip', this.container);
this.container.appendChild(this.content);
// 高德地图需要阻止事件冒泡 // 测试mapbox 地图不需要添加
if (stopPropagation) {
['mousemove', 'mousedown', 'mouseup', 'click', 'dblclick'].forEach(
(type) => {
this.container.addEventListener(type, (e) => {
e.stopPropagation();
});
},
);
}
this.container.style.whiteSpace = 'nowrap';
}
// 设置 Popup 的最大宽度
if (maxWidth && this.container.style.maxWidth !== maxWidth) {
this.container.style.maxWidth = maxWidth;
}
this.updateLngLatPosition();
DOM.setTransform(this.container, `${anchorTranslate[anchor]}`);
applyAnchorClass(this.container, anchor, 'popup');
};
/**
* Popup Position
* @param left
* @param top
* @protected
*/
protected setPopupPosition(left: number, top: number) {
if (this.container) {
const { offsets } = this.popupOption;
this.container.style.left = left + offsets[0] + 'px';
this.container.style.top = top - offsets[1] + 'px';
}
}
/**
* option keys
* @param option
* @param keys
* @protected
*/
protected checkUpdateOption(option: Partial<O>, keys: Array<keyof O>) {
return keys.some((key) => key in option);
}
/**
* HTML Fragment
* @param html
* @protected
*/
protected getPopupHTMLFragment(html: PopupHTML) {
const frag = window.document.createDocumentFragment();
const temp = window.document.createElement('body');
let child: ChildNode | null;
if (typeof html === 'string') {
temp.innerHTML = html;
} else if (Array.isArray(html)) {
temp.append(...html);
} else if (html instanceof HTMLElement) {
temp.append(html);
}
while (true) {
child = temp.firstChild;
if (!child) {
break;
}
frag.appendChild(child);
}
return frag;
}
}

View File

@ -0,0 +1,9 @@
export const createL7Icon = (className: string) => {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.classList.add('l7-iconfont');
svg.setAttribute('aria-hidden', 'true');
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', `#${className}`);
svg.appendChild(use);
return svg;
};

View File

@ -0,0 +1,286 @@
import { DOM } from '@antv/l7-utils';
import { EventEmitter } from 'eventemitter3';
/**
*
*/
export type PopperPlacement =
| 'top-start'
| 'top'
| 'top-end'
| 'left-start'
| 'left'
| 'left-end'
| 'bottom-start'
| 'bottom'
| 'bottom-end'
| 'right-start'
| 'right'
| 'right-end';
/**
* click hover
*/
export type PopperTrigger = 'click' | 'hover';
/**
*
*/
export type PopperContent = string | HTMLElement | null;
export interface IPopperOption {
placement: PopperPlacement; // 气泡展示方向
trigger: PopperTrigger; // 气泡触发方式
content?: PopperContent; // 初始内容
offset?: [number, number]; // 气泡偏移
className?: string; // 容器自定义 className
container: HTMLElement; // 触发气泡的容器
unique?: boolean; // 当前气泡展示时,是否关闭其他该配置为 true 的气泡
}
export class Popper extends EventEmitter<'show' | 'hide'> {
protected get buttonRect() {
return this.button.getBoundingClientRect();
}
protected static conflictPopperList: Popper[] = [];
// 气泡容器 DOM
public popperDOM!: HTMLElement;
// 气泡中展示的内容容器 DOM
public contentDOM!: HTMLElement;
/**
*
* @protected
*/
protected button: HTMLElement;
/**
* Popper
* @protected
*/
protected option: IPopperOption;
/**
*
* @protected
*/
protected isShow: boolean = false;
/**
*
* @protected
*/
protected content: PopperContent;
/**
*
* @protected
*/
protected timeout: number | null = null;
constructor(button: HTMLElement, option: IPopperOption) {
super();
this.button = button;
this.option = option;
this.init();
if (option.unique) {
Popper.conflictPopperList.push(this);
}
}
public getPopperDOM() {
return this.popperDOM;
}
public getIsShow() {
return this.isShow;
}
public getContent() {
return this.content;
}
public setContent(content: PopperContent) {
if (typeof content === 'string') {
this.contentDOM.innerHTML = content;
} else if (content instanceof HTMLElement) {
DOM.clearChildren(this.contentDOM);
this.contentDOM.appendChild(content);
}
this.content = content;
}
public show = () => {
if (this.isShow || !this.contentDOM.innerHTML) {
return this;
}
this.resetPopperPosition();
DOM.removeClass(this.popperDOM, 'l7-popper-hide');
this.isShow = true;
if (this.option.unique) {
// console.log(Popper.conflictPopperList.length);
Popper.conflictPopperList.forEach((popper) => {
if (popper !== this && popper.isShow) {
popper.hide();
}
});
}
this.emit('show');
return this;
};
public hide = () => {
if (!this.isShow) {
return this;
}
DOM.addClass(this.popperDOM, 'l7-popper-hide');
this.isShow = false;
this.emit('hide');
return this;
};
/**
*
*/
public setHideTimeout = () => {
if (this.timeout) {
return;
}
this.timeout = window.setTimeout(() => {
if (!this.isShow) {
return;
}
this.hide();
this.timeout = null;
}, 300);
};
/**
*
*/
public clearHideTimeout = () => {
if (this.timeout) {
window.clearTimeout(this.timeout);
this.timeout = null;
}
};
public init() {
const { trigger } = this.option;
this.popperDOM = this.createPopper();
if (trigger === 'click') {
this.button.addEventListener('click', this.onBtnClick);
} else {
this.button.addEventListener('mousemove', this.onBtnMouseMove);
this.button.addEventListener('mouseleave', this.onBtnMouseLeave);
this.popperDOM.addEventListener('mousemove', this.onBtnMouseMove);
this.popperDOM.addEventListener('mouseleave', this.onBtnMouseLeave);
}
}
public destroy() {
this.button.removeEventListener('click', this.onBtnClick);
this.button.removeEventListener('mousemove', this.onBtnMouseMove);
this.button.removeEventListener('mousemove', this.onBtnMouseLeave);
this.popperDOM.removeEventListener('mousemove', this.onBtnMouseMove);
this.popperDOM.removeEventListener('mouseleave', this.onBtnMouseLeave);
DOM.remove(this.popperDOM);
}
public resetPopperPosition() {
const popperStyleObj: any = {};
const { container, offset = [0, 0], placement } = this.option;
const [offsetX, offsetY] = offset;
const buttonRect = this.button.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
const { left, right, top, bottom } = DOM.getDiffRect(
buttonRect,
containerRect,
);
let isTransformX = false;
let isTransformY = false;
if (/^(left|right)/.test(placement)) {
if (placement.includes('left')) {
popperStyleObj.right = `${buttonRect.width + right}px`;
} else if (placement.includes('right')) {
popperStyleObj.left = `${buttonRect.width + left}px`;
}
if (placement.includes('start')) {
popperStyleObj.top = `${top}px`;
} else if (placement.includes('end')) {
popperStyleObj.bottom = `${bottom}px`;
} else {
popperStyleObj.top = `${top + buttonRect.height / 2}px`;
isTransformY = true;
popperStyleObj.transform = `translate(${offsetX}px, calc(${offsetY}px - 50%))`;
}
} else if (/^(top|bottom)/.test(placement)) {
if (placement.includes('top')) {
popperStyleObj.bottom = `${buttonRect.height + bottom}px`;
} else if (placement.includes('bottom')) {
popperStyleObj.top = `${buttonRect.height + top}px`;
}
if (placement.includes('start')) {
popperStyleObj.left = `${left}px`;
} else if (placement.includes('end')) {
popperStyleObj.right = `${right}px`;
} else {
popperStyleObj.left = `${left + buttonRect.width / 2}px`;
isTransformX = true;
popperStyleObj.transform = `translate(calc(${offsetX}px - 50%), ${offsetY}px)`;
}
}
popperStyleObj.transform = `translate(calc(${offsetX}px - ${
isTransformX ? '50%' : '0%'
}), calc(${offsetY}px - ${isTransformY ? '50%' : '0%'})`;
const posList = placement.split('-');
if (posList.length) {
DOM.addClass(
this.popperDOM,
posList.map((pos) => `l7-popper-${pos}`).join(' '),
);
}
DOM.addStyle(this.popperDOM, DOM.css2Style(popperStyleObj));
}
protected createPopper(): HTMLElement {
const { container, className = '', content } = this.option;
const popper = DOM.create(
'div',
`l7-popper l7-popper-hide ${className}`,
) as HTMLElement;
const popperContent = DOM.create('div', 'l7-popper-content') as HTMLElement;
const popperArrow = DOM.create('div', 'l7-popper-arrow') as HTMLElement;
popper.appendChild(popperContent);
popper.appendChild(popperArrow);
container.appendChild(popper);
this.popperDOM = popper;
this.contentDOM = popperContent;
if (content) {
this.setContent(content);
}
return popper;
}
protected onBtnClick = () => {
if (this.isShow) {
this.hide();
} else {
this.show();
}
};
protected onBtnMouseLeave = () => {
this.setHideTimeout();
};
protected onBtnMouseMove = () => {
this.clearHideTimeout();
if (this.isShow) {
return;
}
this.show();
};
}

View File

@ -0,0 +1,159 @@
// @ts-nocheck
const methodMap = [
[
'requestFullscreen',
'exitFullscreen',
'fullscreenElement',
'fullscreenEnabled',
'fullscreenchange',
'fullscreenerror',
],
// New WebKit
[
'webkitRequestFullscreen',
'webkitExitFullscreen',
'webkitFullscreenElement',
'webkitFullscreenEnabled',
'webkitfullscreenchange',
'webkitfullscreenerror',
],
// Old WebKit
[
'webkitRequestFullScreen',
'webkitCancelFullScreen',
'webkitCurrentFullScreenElement',
'webkitCancelFullScreen',
'webkitfullscreenchange',
'webkitfullscreenerror',
],
[
'mozRequestFullScreen',
'mozCancelFullScreen',
'mozFullScreenElement',
'mozFullScreenEnabled',
'mozfullscreenchange',
'mozfullscreenerror',
],
[
'msRequestFullscreen',
'msExitFullscreen',
'msFullscreenElement',
'msFullscreenEnabled',
'MSFullscreenChange',
'MSFullscreenError',
],
];
const nativeAPI = (() => {
if (typeof document === 'undefined') {
return false;
}
const unprefixedMethods = methodMap[0];
const returnValue = {};
for (const methodList of methodMap) {
const exitFullscreenMethod = methodList?.[1];
if (exitFullscreenMethod in document) {
for (const [index, method] of methodList.entries()) {
returnValue[unprefixedMethods[index]] = method;
}
return returnValue;
}
}
return false;
})();
const eventNameMap = {
change: nativeAPI.fullscreenchange,
error: nativeAPI.fullscreenerror,
};
let screenfull: any = {
// eslint-disable-next-line default-param-last
request(element = document.documentElement, options) {
return new Promise((resolve, reject) => {
const onFullScreenEntered = () => {
screenfull.off('change', onFullScreenEntered);
resolve();
};
screenfull.on('change', onFullScreenEntered);
const returnPromise = element[nativeAPI.requestFullscreen](options);
if (returnPromise instanceof Promise) {
returnPromise.then(onFullScreenEntered).catch(reject);
}
});
},
exit() {
return new Promise((resolve, reject) => {
if (!screenfull.isFullscreen) {
resolve();
return;
}
const onFullScreenExit = () => {
screenfull.off('change', onFullScreenExit);
resolve();
};
screenfull.on('change', onFullScreenExit);
const returnPromise = document[nativeAPI.exitFullscreen]();
if (returnPromise instanceof Promise) {
returnPromise.then(onFullScreenExit).catch(reject);
}
});
},
toggle(element, options) {
return screenfull.isFullscreen
? screenfull.exit()
: screenfull.request(element, options);
},
onchange(callback) {
screenfull.on('change', callback);
},
onerror(callback) {
screenfull.on('error', callback);
},
on(event, callback) {
const eventName = eventNameMap[event];
if (eventName) {
document.addEventListener(eventName, callback, false);
}
},
off(event, callback) {
const eventName = eventNameMap[event];
if (eventName) {
document.removeEventListener(eventName, callback, false);
}
},
raw: nativeAPI,
};
Object.defineProperties(screenfull, {
isFullscreen: {
get: () => Boolean(document[nativeAPI.fullscreenElement]),
},
element: {
enumerable: true,
get: () => document[nativeAPI.fullscreenElement] ?? undefined,
},
isEnabled: {
enumerable: true,
// Coerce to boolean in case of old WebKit.
get: () => Boolean(document[nativeAPI.fullscreenEnabled]),
},
});
if (!nativeAPI) {
screenfull = { isEnabled: false };
}
export default screenfull;

View File

@ -8,7 +8,25 @@ import {
IControlCorners,
IControlService,
IControlServiceCfg,
PositionName,
PositionType,
} from './IControlService';
const ControlDirectionConfig: Record<PositionName, 'column' | 'row'> = {
topleft: 'column',
topright: 'column',
bottomright: 'column',
bottomleft: 'column',
leftcenter: 'column',
rightcenter: 'column',
topcenter: 'row',
bottomcenter: 'row',
lefttop: 'row',
righttop: 'row',
leftbottom: 'row',
rightbottom: 'row',
};
@injectable()
export default class ControlService implements IControlService {
public container: HTMLElement;
@ -74,18 +92,21 @@ export default class ControlService implements IControlService {
function createCorner(vSideList: string[] = []) {
const className = vSideList.map((item) => l + item).join(' ');
corners[vSideList.join('')] = DOM.create('div', className, container);
corners[
vSideList.filter((item) => !['row', 'column'].includes(item)).join('')
] = DOM.create('div', className, container);
}
createCorner(['top', 'left']);
createCorner(['top', 'right']);
createCorner(['bottom', 'left']);
createCorner(['bottom', 'right']);
function getCornerClassList(positionName: PositionName) {
const positionList = positionName
.replace(/^(top|bottom|left|right|center)/, '$1-')
.split('-');
return [...positionList, ControlDirectionConfig[positionName]];
}
createCorner(['top', 'center']);
createCorner(['right', 'center']);
createCorner(['left', 'center']);
createCorner(['bottom', 'center']);
Object.values(PositionType).forEach((position) => {
createCorner(getCornerClassList(position));
});
}
private clearControlPos() {

View File

@ -1,4 +1,5 @@
import { Container } from 'inversify';
export enum PositionType {
'TOPRIGHT' = 'topright',
'TOPLEFT' = 'topleft',
@ -8,6 +9,10 @@ export enum PositionType {
'BOTTOMCENTER' = 'bottomcenter',
'LEFTCENTER' = 'leftcenter',
'RIGHTCENTER' = 'rightcenter',
'LEFTTOP' = 'lefttop',
'RIGHTTOP' = 'righttop',
'LEFTBOTTOM' = 'leftbottom',
'RIGHTBOTTOM' = 'rightbottom',
}
export type PositionName =
@ -18,21 +23,21 @@ export type PositionName =
| 'topcenter'
| 'bottomcenter'
| 'leftcenter'
| 'rightcenter';
export interface IControlOption {
name: string;
position: PositionName;
[key: string]: any;
}
| 'rightcenter'
| 'lefttop'
| 'righttop'
| 'leftbottom'
| 'rightbottom';
export interface IControlServiceCfg {
container: HTMLElement;
}
export interface IControlCorners {
[key: string]: HTMLElement;
}
export interface IControl {
controlOption: IControlOption;
setPosition(pos: PositionType): void;
export interface IControl<O = any> {
controlOption: O;
setOptions: (newOption: Partial<O>) => void;
addTo(sceneContainer: Container): void;
onAdd(): HTMLElement;
onRemove(): void;

View File

@ -1,29 +1,112 @@
import { anchorType } from '@antv/l7-utils';
import EventEmitter from 'eventemitter3';
import { Container } from 'inversify';
import { ILngLat } from '../map/IMapService';
export type PopupHTML = string | HTMLElement | HTMLElement[];
export interface IPopupOption {
/**
*
*/
closeButton: boolean;
/**
*
*/
closeButtonOffsets?: [number, number];
/**
*
*/
closeOnClick: boolean;
/**
* Esc
*/
closeOnEsc: boolean;
/**
*
*/
maxWidth: string;
/**
*
*/
anchor: anchorType[any];
className: string;
offsets: number[];
/**
*
*/
offsets: [number, number];
/**
*
*/
stopPropagation: boolean;
/**
* popup
*/
autoPan: boolean;
/**
*
*/
autoClose: boolean;
/**
*
*/
followCursor: boolean;
/**
* class
*/
className?: string;
/**
* style
*/
style?: string;
/**
* Popup
*/
text?: string;
/**
* Popup HTML
*/
html?: PopupHTML;
/**
* Popup
*/
title?: PopupHTML;
/**
*
*/
lngLat?: ILngLat;
}
export interface IPopup {
export interface IPopup extends EventEmitter {
addTo(scene: Container): this;
remove(): void;
setLnglat(lngLat: ILngLat): this;
getLnglat(): ILngLat;
setHTML(html: string): this;
setHTML(html: PopupHTML): this;
setText(text: string): this;
setMaxWidth(maxWidth: string): this;
isOpen(): boolean;
open(): void;
close(): void;
open(): this;
close(): this;
getOptions(): IPopupOption;
setOptions(option: Partial<IPopupOption>): this;
}
export interface IPopupService {
addPopup(popup: IPopup): void;
removePopup(popup: IPopup): void;

View File

@ -7,33 +7,60 @@ import { IPopup, IPopupService } from './IPopupService';
@injectable()
export default class PopupService implements IPopupService {
private scene: Container;
private popup: IPopup;
private mapsService: IMapService;
private unAddPopup: IPopup | null;
private popups: IPopup[] = [];
private unAddPopups: IPopup[] = [];
public get isMarkerReady() {
return this.mapsService.map && this.mapsService.getMarkerContainer();
}
public removePopup(popup: IPopup): void {
if (popup.isOpen()) {
popup.remove();
}
const targetIndex = this.popups.indexOf(popup);
if (targetIndex > -1) {
this.popups.splice(targetIndex, 1);
}
const targetUnAddIndex = this.unAddPopups.indexOf(popup);
if (targetUnAddIndex > -1) {
this.unAddPopups.splice(targetUnAddIndex, 1);
}
}
public destroy(): void {
this.popup.remove();
this.popups.forEach((popup) => popup.remove());
}
public addPopup(popup: IPopup) {
if (this.popup) {
this.popup.remove();
if (popup && popup.getOptions().autoClose) {
[...this.popups, ...this.unAddPopups].forEach((otherPopup) => {
if (otherPopup.getOptions().autoClose) {
this.removePopup(otherPopup);
}
if (this.mapsService.map && this.mapsService.getMarkerContainer()) {
});
}
if (this.isMarkerReady) {
popup.addTo(this.scene);
this.popup = popup;
this.popups.push(popup);
} else {
this.unAddPopup = popup;
this.unAddPopups.push(popup);
}
popup.on('close', () => {
this.removePopup(popup);
});
}
public initPopup() {
if (this.unAddPopup) {
this.addPopup(this.unAddPopup);
this.unAddPopup = null;
if (this.unAddPopups.length) {
this.unAddPopups.forEach((popup) => {
this.addPopup(popup);
this.unAddPopups = [];
});
}
}

Some files were not shown because too many files have changed in this diff Show More