/** * 带图标的点 */ import { action, computed, makeObservable, observable, reaction } from 'mobx'; import mapbox from 'mapbox-gl'; import * as turf from '@turf/turf'; import EventDispatcher from '../EventDispatcher'; import Icon from './Icon'; class IconPointRenderer extends EventDispatcher { _topic = null; _map = null; _icons = {}; _dataSource = []; get _sourceId() { return `${this._topic}-icon-point-source`; } get _layerId() { return `${this._topic}-icon-point-layer`; } get _pointFeatures() { return this._dataSource.map(({ id, iconName, point, ...others }) => { const { [iconName]: icon } = this._icons; const { zoom: iconZoom = 1, offset: iconOffset = [0, 0] } = icon || {}; const feature = turf.point(point, { id, iconName, iconZoom, iconOffset, ...others }); feature.id = id; return feature; }); } get _pointFeatureCollection() { return turf.featureCollection(this._pointFeatures); } constructor(topic, icons = []) { super(['mousemove', 'mouseleave', 'mouseenter']); this._topic = topic; if (!icons.length) { throw new Error('入参icons至少包含一个图标元素'); } icons.forEach(this.updateIcon); makeObservable(this, { _topic: observable, _icons: observable, _dataSource: observable, _sourceId: computed, _layerId: computed, _pointFeatures: computed, _pointFeatureCollection: computed, loadDataSource: action, updateIcon: action, destroy: action, }); reaction(() => this._dataSource, () => { this._render(); }); reaction(() => this._icons, () => { this._render(); }); } setMap(mapInstance) { if (this._map === mapInstance) return; if (!(mapInstance instanceof mapbox.Map)) { throw new Error('必须传入一个mapbox地图实例'); } this._map = mapInstance; } // 载入数据 // list->item必须包含id、point、iconName(其中point如果是对象则必须包含lng、lat属性,如果是数组则必须是[lng, lat]) // list->item可以包含rotate,表示旋转角度 loadDataSource(list) { if (!this._map) { throw new Error('请先设置地图实例'); } this._dataSource = (list || []).map(item => { const { id, iconName, point } = item; const newItem = { ...item }; if (!Array.isArray(point)) { const { lng, lat } = point; newItem.point = [lng, lat]; } return (id >= 0 && iconName && Array.isArray(newItem.point)) ? newItem : null; }).filter(Boolean); if (!this._dataSource.length) { console.warn('数据列表为空'); } } updateIcon = icon => { if (!(icon instanceof Icon)) { throw new Error(`入参icon必须是${Icon}的实例`); } this._icons = { ...this._icons, [icon.name]: icon, }; }; _loadIcons() { if (!this._map) return; Object.values(this._icons).forEach(icon => { if (this._map.hasImage(icon.name)) { this._map.updateImage(icon.name, icon.data); } else { this._map.addImage(icon.name, icon.data); } }); } _render() { if (!this._map) return; this._loadIcons(); this._renderPoints(); } _renderPoints() { const source = this._map.getSource(this._sourceId); if (!source) { this._map.addSource(this._sourceId, { type: 'geojson', data: this._pointFeatureCollection, }); this._map.addLayer({ id: this._layerId, type: 'symbol', source: this._sourceId, paint: { 'icon-opacity': [ 'case', ['boolean', ['feature-state', 'visible'], true], 1, 0, ], // 'icon-translate': ['get', 'iconOffset'], // 缩放之后平移的像素值(mark:icon-translate不支持表达式) }, layout: { 'icon-image': ['get', 'iconName'], 'icon-size': ['get', 'iconZoom'], 'icon-offset': ['get', 'iconOffset'], // 乘以缩放值后,才是最终偏移量 'icon-rotate': ['coalesce', ['get', 'rotate'], ['number', 0]], 'icon-allow-overlap': true, }, }); this._map.on('click', this._layerId, this._onClick); this._map.on('mouseenter', this._layerId, this._onMouseEnter); this._map.on('mousemove', this._layerId, this._onMouseMove); this._map.on('mouseleave', this._layerId, this._onMouseLeave); } else { source.setData(this._pointFeatureCollection); } } _onClick = e => { if (!e.features.length) return; const { lngLat, point } = e; const [{ properties: detail }] = e.features; this._trigger('click', { lngLat, point, detail }); }; _onMouseMove = () => { this._map.getCanvas().style.cursor = 'pointer'; }; _onMouseEnter = e => { if (!e.features.length) return; const { lngLat, point } = e; const [{ properties: detail }] = e.features; this._trigger('mouseenter', { lngLat, point, detail }); }; _onMouseLeave = e => { this._map.getCanvas().style.cursor = ''; const { lngLat, point } = e; this._trigger('mouseleave', { lngLat, point }); }; // 是否包含某个点 hasPoint(pointId) { return this._dataSource.findIndex(({ id }) => pointId === id) >= 0; } // 隐藏某个点 hidePoint(pointId) { if (!this._map || !this.hasPoint(pointId)) return; if (this._map.getSource(this._sourceId)) this._map.setFeatureState({ source: this._sourceId, id: pointId }, { visible: false }); } // 显示某个点 showPoint(pointId) { if (!this._map || !this.hasPoint(pointId)) return; if (this._map.getSource(this._sourceId)) this._map.setFeatureState({ source: this._sourceId, id: pointId }, { visible: true }); } changeVisibility(stateValue) { if (!this._map) return; if (this._map.getLayer(this._layerId)) { this._map.setLayoutProperty(this._layerId, 'visibility', stateValue ? 'visible' : 'none'); } } clear() { if (!this._map) return; if (this._map.getSource(this._sourceId)) { this._map.off('click', this._layerId, this._onClick); this._map.off('mousemove', this._layerId, this._onMouseMove); this._map.off('mouseleave', this._layerId, this._onMouseLeave); this._map.off('mouseenter', this._layerId, this._onMouseEnter); this._map.removeLayer(this._layerId); this._map.removeSource(this._sourceId); } } destroy() { this.icons = {}; this._dataSource = []; this.clear(); this._map = null; } } export default IconPointRenderer;