feat(zhst/map): 添加例子

This commit is contained in:
NICE CODE BY DEV 2024-05-30 09:13:15 +08:00
parent 3bd50deeb3
commit 3666fc4ed1
39 changed files with 471 additions and 34 deletions

View File

@ -0,0 +1,91 @@
import React, { useRef, useEffect, useState } from 'react';
import { MapBox, MapProps } from '@zhst/map';
import axios from 'axios';
import { FloatButton, Switch } from '@zhst/meta';
const demo = () => {
const [showCluster, setShowCluster] = useState(true)
const [sluterData, setSluterData] = useState<MapProps['clusterData']>()
const mapRef = useRef(null);
// 初始化
const handleMapLoad = (e: mapboxgl.MapboxEvent<undefined>) => {
const map = e.target;
if (!map) return
map.flyTo({
// center: [120,30],
// zoom: map?.getMaxZoom(),
});
};
const getData = async () => {
let res = await axios({
method: 'post',
url: 'http://10.0.0.120:30003/singer.DeviceService/ListCamera',
headers: {
Authorization: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTcwNDAzNzEsImp0aSI6IjExMjgiLCJpYXQiOjE3MTY3ODExNzEsInVzZXJJZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsImRhdGFSaWdodCI6MiwiY2FtZXJhUmlnaHQiOjEsImdwdVJpZ2h0IjoxLCJ1c2VybGVhZGVySWQiOjAsIm9yZ2FuaXphdGlvbklkIjoxLCJyb2xlSWQiOjF9.XHbXIkXkfUuvqV6_qSV4d20xj-s7I0qOQZgL-zspMDc'
},
data: {"labelData":[],"filter":{"realtimeProcessingFilter":0,"cameraFilter":[{"opt":"ORNOT","cameraOpt":"CAMERAOPT_TYPE","value":"100"}]},"maxResults":50}
});
if (res.status === 200) {
let markers = []
let sluters = {
features: []
}
res.data.cameras?.forEach(camera => {
markers.push({
key: camera.id,
id: camera.id,
title: camera.name,
population: camera.id,
checked: false,
disabled: false,
showCheckBox: true,
status: ['blue', 'yellow', 'red'][Math.floor(Math.random() * 3)],
latitude: camera.latitude,
longitude: camera.longitude
})
sluters.features.push({
"geometry": {
"type": "Point",
"coordinates": [
camera.longitude,
camera.latitude,
50
]
}
})
})
setSluterData(sluters)
}
}
useEffect(() => {
getData()
}, [])
return (
<div>
<FloatButton>
<Switch value={true}/>
</FloatButton>
<MapBox
onLoad={handleMapLoad}
ref={mapRef}
showToolBar={false}
width='100%'
height='800px'
showCluster={showCluster}
clusterProps={{
data: sluterData
}}
// customMarkerRender={(_data) => <div>自定义标记</div>}
>
</MapBox>
</div>
);
};
export default demo;

View File

@ -0,0 +1,24 @@
---
category: Components
subtitle: 聚合点
title: Cluster 聚合点
toc: content
group:
title: 数据展示
order: 1
---
<code src="./demo/basic.tsx">基本用法</code>
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| clusterData | 聚合数据集合 | mapboxgl.GeoJSONSourceRaw['data'] | {} | - |
| showCluster | 显示/隐藏聚合点 | boolean | {} | - |
| type | 数据接收类型对clusterData生效 | string | geojson | - |
| cluster | 是否打开聚合功能 | boolean | {} | - |
| clusterMaxZoom | 最大生效层级 | number | 14 | - |
| clusterRadius | 聚合半径 | number | 50 | - |
> 更多参数参考 react-map-gl 的 Source 组件

View File

@ -14,13 +14,14 @@ import {
import drawRectMode from 'mapbox-gl-draw-rectangle-mode'
// @ts-ignore
import drawStaticMode from '@mapbox/mapbox-gl-draw-static-mode'
// @ts-ignore
import drawCircleMode from './Draw/drawCircleMode.draw.js'
import { useControl } from 'react-map-gl';
import type { ControlPosition } from 'react-map-gl';
import { MapContextValue } from 'react-map-gl/dist/esm/components/map';
export type DrawControlProps = ConstructorParameters<typeof MapboxDraw>[0] & {
position?: ControlPosition;
onCreate?: (evt: {features: object[]}) => void;
onUpdate?: (evt: {features: object[]; action: string}) => void;
onDelete?: (evt: {features: object[]}) => void;
@ -50,7 +51,7 @@ const DrawControl = forwardRef<DrawControlRefProps, DrawControlProps>((props, re
// draw_line_select: drawLineSelectMode,
draw_rect: drawRectMode,
drag_circle: DragCircleMode,
draw_circle : CircleMode,
draw_circle : drawCircleMode,
direct_select: DirectMode,
simple_select: SimpleSelectMode,
static: drawStaticMode,
@ -101,7 +102,8 @@ const DrawControl = forwardRef<DrawControlRefProps, DrawControlProps>((props, re
DrawControl.defaultProps = {
onCreate: () => {},
onUpdate: () => {},
onDelete: () => {}
onDelete: () => {},
};
export default DrawControl

View File

@ -0,0 +1,79 @@
import React, { useRef, useEffect, useState } from 'react';
import { getDistancesByStringLine, MapBox, Marker } from '@zhst/map';
import { FloatButton, Switch } from '@zhst/meta';
const demo = () => {
const mapRef = useRef(null);
const [canDraw, setCanDraw] = useState(false)
const [toolsBarOpen, setToolsBarOpen] = useState(false)
const [rangingList, setRangingList] = useState([])
// 初始化
const handleMapLoad = (e: mapboxgl.MapboxEvent<undefined>) => {
const map = e.target;
if (!map) return
map.flyTo({
// center: [120,30],
// zoom: map?.getMaxZoom(),
});
};
useEffect(() => {
}, [])
return (
<div>
<FloatButton>
<Switch value={true}/>
</FloatButton>
<MapBox
onLoad={handleMapLoad}
ref={mapRef}
width='100%'
height='800px'
draw={canDraw}
toolsBarOpen={toolsBarOpen}
onToolClick={e => {
setCanDraw(pre => !pre)
setToolsBarOpen(pre => !pre)
}}
onDrawCreate={e => {
const geojson = e.features[0]
// 防止绘制完成后,地图自动切换到选中状态
setTimeout(async () => {
mapRef.current.drawer.changeMode('simple_select')
}, 100);
const polygonJson = getDistancesByStringLine(geojson) || []
setRangingList(polygonJson.distanceArr)
}}
onDrawDelete={e => setRangingList([])}
onDrawUpdate={e => {
const geojson = e.features[0]
const polygonJson = getDistancesByStringLine(geojson) || []
setRangingList(polygonJson.distanceArr)
}}
drawerProps={{
onActionable: e => console.log('e', e)
}}
>
{rangingList?.map((item, index) => {
return (
<Marker
key={index}
anchor="bottom"
longitude={item.to[0]}
latitude={item.to[1]}
// !! 阻止原生的冒泡
onClick={e => e.originalEvent.stopPropagation()}
>
<div style={{ padding: '3px 6px', background: '#fff', border: '1px solid #000' }} >{(item.totalLength)}km</div>
</Marker>
)
})}
</MapBox>
</div>
);
};
export default demo;

View File

@ -0,0 +1,83 @@
---
category: Components
subtitle: 地图绘制
title: Draw 地图绘制
toc: content
group:
title: 地图操作
order: 1
---
<code src="./demo/basic.tsx">基本用法</code>
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| draw | 是否绘制 | boolean | false | - |
| toolsBarOpen | 工具栏是否打开 | boolean | false | - |
| onToolClick | 工具Icon点击事件 | (e: React.MouseEvent<HTMLElement, MouseEvent>) => void | false | - |
| buttonList | 工具栏按钮列表 | IButtonList | defaultButtonList | - |
| mapRef.current.drawer | 绘制组件的Ref参考@mapbox/mapbox-gl-draw | DrawControlRefProps | - | - |
| onCreate | 创建监听 | (evt: {features: object[]}) => void; | - | - |
| onUpdate | 更新监听 | (evt: {features: object[]}) => void; | - | - |
| onDelete | 删除监听 | (evt: {features: object[]}) => void; | - | - |
| onRender | 渲染监听 | (evt: {features: object[]}) => void; | - | - |
| onCombine | 组合监听 | (evt: {features: object[]}) => void; | - | - |
| onUncombine | 分离监听 | (evt: {features: object[]}) => void; | - | - |
| onModeChange | 模式切换监听 | (evt: {features: object[]}) => void; | - | - |
| onActionable | 是否绘制 | (evt: {features: object[]}) => void; | - | - |
| onSelectionChange | 选中目标切换监听 | (evt: {features: object[]}) => void; | - | - |
```js
interface IButtonList {
label: string
key: string
icon?: ReactNode | string
onClick?: () => void
}[]
// 默认按钮列表
const defaultButtonList = [
{
label: '圆形框选',
key: 'circle',
icon: 'icon-yuan',
onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_circle')
},
{
label: '矩形框选',
key: 'rect',
icon: 'icon-fang',
onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_rect'),
popoverProps: {
placement: 'bottom',
content: '自定义内容'
}
},
{
label: '多边形框选',
key: 'more',
icon: 'icon-duobianxing',
onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_polygon')
},
{
label: '路径框选',
key: 'path',
icon: 'icon-lujingkuangxuannor',
onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_line_string')
},
{
label: '测距',
key: 'path',
icon: 'icon-ceju',
onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_line_string')
},
{
label: '清除',
key: 'clear',
icon: 'icon-gongjuxiangguanbi',
onClick: () => drawControlRef.current?.drawer?.deleteAll()
}
]
```

View File

@ -1,2 +1,4 @@
export { default as MapBox } from './MapBox';
export { default as MapBox } from './mapBox';
export type { MapProps, MapRefProps } from './mapBox';
export * from 'react-map-gl'
export * from './utils'

View File

@ -6,15 +6,15 @@ import { CSSProperties } from "react";
import { MapRef, MapStyle, MapProps as MapBoxProps } from "react-map-gl";
import classnames from 'classnames'
import { merge } from '@zhst/func'
import Tools, { ToolsProps } from './components/tools'
import DrawControl, { DrawControlProps, DrawControlRefProps } from './components/drawControl';
import { defaultMapConfig } from './utils/constants';
import Tools, { ToolsProps } from '../tools'
import DrawControl, { DrawControlProps, DrawControlRefProps } from '../drawControl';
import { defaultMapConfig } from '../utils/constants';
import './index.less';
import mapboxDrawStyle from './utils/drawStyle';
import Marker, { MarkerProps } from './components/marker';
import PopUp, { PopUpProps } from './components/popup';
import Cluster, { ClusterProps } from './components/clusters/Clusters';
import { clusterLayer } from './components/clusters/layers';
import mapboxDrawStyle from '../utils/drawStyle';
import Marker, { MarkerProps } from '../marker';
import PopUp, { PopUpProps } from '../popup';
import Cluster from '../clusters/Clusters';
import { clusterLayer } from '../clusters/layers';
const componentName = 'zhst-map'
@ -29,15 +29,17 @@ export interface MapProps extends MapBoxProps {
mapRef?: MapRef
style?: CSSProperties
children?: JSX.Element | JSX.Element[] | Array<JSX.Element | undefined>
sluterData?: any;
clusterData?: mapboxgl.GeoJSONSourceRaw['data'];
draw?: boolean
showMarker?: boolean // 显示标记点
showCluster?: boolean // 显示范围统计
showToolBar?: boolean // 是否显示工具箱
buttonList?: ToolsProps['buttonList']
popUpInfo?: PopUpProps
showPopUp?: boolean
clusterProps?: ClusterProps
clusterProps?: Partial<mapboxgl.GeoJSONSourceRaw>
toolsBarOpen?: boolean
drawerProps?: DrawControlProps
customMarkerRender?: MarkerProps['customMarkerRender']
onLoad?: (e: mapboxgl.MapboxEvent<undefined>) => void
onDrawCreate?: (e: { features: object[], [key: string]: any }) => void
@ -49,6 +51,7 @@ export interface MapProps extends MapBoxProps {
}
export interface MapRefProps {
drawer: DrawControlRefProps
}
const MapBox = forwardRef<MapRefProps, MapProps>((props, ref) => {
@ -59,23 +62,25 @@ const MapBox = forwardRef<MapRefProps, MapProps>((props, ref) => {
width = '100%',
draw,
markerData = [],
sluterData = [],
clusterData,
popUpInfo = {
longitude: 0,
latitude: 0
},
showPopUp,
toolsBarOpen,
showMarker = true,
showMarker = false,
showCluster = false,
showToolBar = true,
clusterProps,
drawerProps,
interactiveLayerIds = [],
buttonList = [
{
label: '圆形框选',
key: 'circle',
icon: 'icon-yuan',
onClick: () => drawControlRef.current?.drawer?.changeMode?.('simple_select')
onClick: () => drawControlRef.current?.drawer?.changeMode?.('draw_circle')
},
{
label: '矩形框选',
@ -152,17 +157,20 @@ const MapBox = forwardRef<MapRefProps, MapProps>((props, ref) => {
useImperativeHandle(ref, () => ({
mapRef: mapRef.current,
drawer: drawControlRef.current?.drawer,
// @ts-ignore
drawer: drawControlRef.current?.drawer!,
}))
return (
//@ts-ignore
<div className={classnames(`${componentName}`)}>
<Tools
open={toolsBarOpen}
buttonList={buttonList}
onToolClick={onToolClick}
/>
{showToolBar && (
<Tools
open={toolsBarOpen}
buttonList={buttonList}
onToolClick={onToolClick}
/>
)}
{/* @ts-ignore */}
<Map
ref={mapRef}
@ -190,11 +198,12 @@ const MapBox = forwardRef<MapRefProps, MapProps>((props, ref) => {
{/* 范围统计标点 */}
{showCluster && !showMarker && (
<Cluster
id={clusterLayer.id}
type="geojson"
cluster={true}
clusterMaxZoom={14}
clusterRadius={50}
data={sluterData}
data={clusterData}
{...clusterProps}
/>
)}
@ -205,8 +214,8 @@ const MapBox = forwardRef<MapRefProps, MapProps>((props, ref) => {
onCreate={onDrawCreate}
onUpdate={onDrawUpdate}
onDelete={onDrawDelete}
onSelectionChange={e => console.log('e', e)}
{...drawConfig}
{...drawerProps}
/>
)}
{children}

View File

@ -0,0 +1,7 @@
/**
* Created by jiangzhixiong on 2024/05/23
*/
import MapBox from './MapBox'
export type { MapProps, MapRefProps } from './MapBox'
export default MapBox

View File

View File

@ -9,11 +9,17 @@ import {
} from 'react-map-gl'
import { Checkbox, ConfigProvider, Image } from '@zhst/meta'
import classNames from 'classnames'
import cameraBlue from '../../assets/icons/camera_blue.png'
import cameraGreen from '../../assets/icons/camera_green.png'
import cameraGrey from '../../assets/icons/camera_grey.png'
import cameraRed from '../../assets/icons/camera_red.png'
import cameraYellow from '../../assets/icons/camera_yellow.png'
import cameraBlue from '../assets/icons/camera_blue.png'
import cameraGreen from '../assets/icons/camera_green.png'
import cameraGrey from '../assets/icons/camera_grey.png'
import cameraRed from '../assets/icons/camera_red.png'
import cameraYellow from '../assets/icons/camera_yellow.png'
import markerBlue from '../assets/icons/marker_blue.png'
import markerEscape from '../assets/icons/marker_escape.png'
import markerGreen from '../assets/icons/marker_green.png'
import markerRedBorder from '../assets/icons/marker_red_border.png'
import markerYellow from '../assets/icons/marker_yellow.png'
import markerRedTrack from '../assets/icons/marker_red_track.png'
import './index.less'
const { ConfigContext } = ConfigProvider
@ -24,6 +30,12 @@ const PIC_MAP = new Map([
['camera_grey', cameraGrey],
['camera_red', cameraRed],
['camera_yellow', cameraYellow],
['marker_blue', markerBlue],
['marker_escape', markerEscape],
['marker_green', markerGreen],
['marker_red_border', markerRedBorder],
['marker_yellow', markerYellow],
['marker_red_track', markerRedTrack],
])
// @ts-ignore
@ -37,10 +49,9 @@ export interface MarkerProps extends MapboxMarkerProps {
showCheckBox?: boolean
showTooltip?: boolean
type?: 'camera' | 'cluster' | 'marker';
population?: string;
status?: 'blue' | 'green' | 'yellow' | 'grey' | 'red_border' | 'escape' | 'escape_border' | 'red_track'; // 摄像头状态
onClick?: (e?: MarkerEvent, data?: MarkerProps) => void;
onMarkerClick?: (e?: MouseEvent, checked?: boolean, data?: MarkerProps) => void;
onCheck?: (e?: MouseEvent, checked?: boolean, data?: MarkerProps) => void;
customMarkerRender?: (data: MarkerProps) => ReactNode
}
@ -57,7 +68,7 @@ const Marker = forwardRef<MarkerRefProps, MarkerProps>((props, ref) => {
showCheckBox,
showTooltip,
onClick,
onMarkerClick,
onCheck,
customMarkerRender,
...rest
} = props
@ -89,7 +100,7 @@ const Marker = forwardRef<MarkerRefProps, MarkerProps>((props, ref) => {
onClick={(e) => {
e.stopPropagation()
// @ts-ignore
onMarkerClick?.(e, checked, props)
onCheck?.(e, checked, props)
}}
/>
)}

View File

@ -0,0 +1,75 @@
import React, { useRef, useEffect, useState } from 'react';
import { MapBox, MarkerProps } from '@zhst/map';
import { FloatButton, Switch } from '@zhst/meta';
import axios from 'axios'
const demo = () => {
const mapRef = useRef(null);
const [markerData, setMarkerData] = useState<MarkerProps[]>([])
const [showMarker, setShowMarker] = useState(true)
// 初始化
const handleMapLoad = (e: mapboxgl.MapboxEvent<undefined>) => {
const map = e.target;
if (!map) return
map.flyTo({
// center: [120,30],
// zoom: map?.getMaxZoom(),
});
};
const getData = async () => {
let res = await axios({
method: 'post',
url: 'http://10.0.0.120:30003/singer.DeviceService/ListCamera',
headers: {
Authorization: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTcwNDAzNzEsImp0aSI6IjExMjgiLCJpYXQiOjE3MTY3ODExNzEsInVzZXJJZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsImRhdGFSaWdodCI6MiwiY2FtZXJhUmlnaHQiOjEsImdwdVJpZ2h0IjoxLCJ1c2VybGVhZGVySWQiOjAsIm9yZ2FuaXphdGlvbklkIjoxLCJyb2xlSWQiOjF9.XHbXIkXkfUuvqV6_qSV4d20xj-s7I0qOQZgL-zspMDc'
},
data: {"labelData":[],"filter":{"realtimeProcessingFilter":0,"cameraFilter":[{"opt":"ORNOT","cameraOpt":"CAMERAOPT_TYPE","value":"100"}]},"maxResults":50}
});
if (res.status === 200) {
let markers = []
res.data.cameras?.forEach(camera => {
markers.push({
key: camera.id,
id: camera.id,
title: camera.name,
checked: false,
disabled: false,
showCheckBox: true,
status: ['blue', 'yellow', 'red'][Math.floor(Math.random() * 3)],
latitude: camera.latitude,
longitude: camera.longitude
})
})
setMarkerData(markers)
}
}
useEffect(() => {
getData()
}, [])
return (
<div>
<FloatButton>
<Switch value={true}/>
</FloatButton>
<MapBox
onLoad={handleMapLoad}
ref={mapRef}
width='100%'
height='800px'
markerData={markerData || []}
showMarker={showMarker}
onMarkerClick={(e, status, data) => {
console.log('marker', data)
}}
>
</MapBox>
</div>
);
};
export default demo;

View File

@ -0,0 +1,53 @@
---
category: Components
subtitle: 标签
title: Marker 标签
toc: content
group:
title: 数据展示
order: 1
---
<code src="./demo/basic.tsx">基本用法</code>
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| key | 键 | string | - | - |
| id | id | string | - | - |
| title | 标题 | string | - | - |
| disabled | 是否可选 | boolean | - | - |
| checked | 是否选中 | boolean | - | - |
| showCheckBox | 是否展示选择框 | boolean | - | - |
| showTooltip | 是否显示tooltip | boolean | - | - |
| type | 标签类型目前只支持camera | 'camera' 'cluster' 'marker' | camera | - |
| status | 状态值 | IStatus | blue | - |
| onClick | 点击事件 | (e?: MarkerEvent, data?: MarkerProps) => void; | - | - |
| onCheck | ☑️选中事件 | (e?: MouseEvent, checked?: boolean, data?: MarkerProps) => void; | - | - |
| customMarkerRender | 自定义marker | (data: MarkerProps) => ReactNode | - | - |
> 更多属性参考react-map-gl 组件: MarkerProps
## IStatus
```js
// 支持的状态,需要配合当前有的标记使用
type IStatus = 'blue' | 'green' | 'yellow' | 'grey' | 'red_border' | 'escape' | 'escape_border' | 'red_track'
// 目前支持的标记
const PIC_MAP = new Map([
['camera_blue', cameraBlue],
['camera_green', cameraGreen],
['camera_grey', cameraGrey],
['camera_red', cameraRed],
['camera_yellow', cameraYellow],
['marker_blue', markerBlue],
['marker_escape', markerEscape],
['marker_green', markerGreen],
['marker_red_border', markerRedBorder],
['marker_yellow', markerYellow],
['marker_red_track', markerRedTrack],
])
```

View File

@ -141,4 +141,5 @@ const mapboxDrawStyle = [
},
},
];
export default mapboxDrawStyle;

View File