You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

185 lines
5.4 KiB

/**
* 测距尺
*/
import mapbox from 'mapbox-gl';
import * as turf from '@turf/turf';
const SOURCE_LINE = 'ruler-source-line';
const SOURCE_SYMBOL = 'ruler-source-symbol';
const LAYER_LINE = 'ruler-layer-line';
const LAYER_SYMBOL = 'ruler-layer-symbol';
const labelFormat = n => (n < 1000 ? `${Number(n).toFixed(2)}` : `${(n / 1000).toFixed(2)}千米`);
class Ruler {
// 地图实例
map = null;
// 点坐标
coordinates = [];
// 距离值(含单位)
labels = [];
// 操作点
markers = [];
// 视觉选项
option = {
mainColor: 'rgba(255,0,0,0.8)',
secondaryColor: '#fff',
fontSize: 12,
};
constructor(map, option = {}) {
this.map = map;
this.option = {
...this.option,
...option,
};
}
// 开启测量(外部调用)
onMeasuringOn() {
this.map.getCanvas().style.cursor = 'crosshair';
this.coordinates = [];
this.markers = [];
this.labels = [];
this.map.on('click', this.onClickMap);
this.map.on('style.load', this.draw);
this.map.fire('ruler.on');
}
// 关闭测量(外部调用)
onMeasuringOff() {
this.map.getCanvas().style.cursor = '';
if (this.map.getSource(SOURCE_LINE)) {
this.map.removeLayer(LAYER_LINE);
this.map.removeSource(SOURCE_LINE);
}
if (this.map.getSource(SOURCE_SYMBOL)) {
this.map.removeLayer(LAYER_SYMBOL);
this.map.removeSource(SOURCE_SYMBOL);
}
this.markers.forEach(m => m.remove());
this.map.off('click', this.onClickMap);
this.map.off('style.load', this.draw);
this.map.fire('ruler.off');
}
// 生成线段geojson
genLineFeature() {
return turf.lineString(this.coordinates);
}
// 生成点geojson
genPointFeature() {
const pointFeatures = this.coordinates.map((coordinate, index) => turf.point(coordinate, {
text: this.labels[index],
}));
return turf.featureCollection(pointFeatures);
}
// 绘制
draw = () => {
if (this.coordinates.length >= 2) {
const sourceLine = this.map.getSource(SOURCE_LINE);
if (!sourceLine) {
this.map.addSource(SOURCE_LINE, {
type: 'geojson',
data: this.genLineFeature(),
});
this.map.addLayer({
id: LAYER_LINE,
type: 'line',
source: SOURCE_LINE,
paint: {
'line-color': this.option.mainColor,
'line-width': 2,
},
});
} else {
sourceLine.setData(this.genLineFeature());
}
}
const sourceSymbol = this.map.getSource(SOURCE_SYMBOL);
if (!sourceSymbol) {
this.map.addSource(SOURCE_SYMBOL, {
type: 'geojson',
data: this.genPointFeature(),
});
this.map.addLayer({
id: LAYER_SYMBOL,
type: 'symbol',
source: SOURCE_SYMBOL,
layout: {
'text-field': '{text}',
'text-anchor': 'top',
'text-size': this.option.fontSize,
'text-offset': [0, 0.8],
'text-allow-overlap': true,
},
paint: {
'text-color': this.option.mainColor,
'text-halo-color': this.option.secondaryColor,
'text-halo-width': 1,
},
});
} else {
sourceSymbol.setData(this.genPointFeature());
}
};
// 生成操作点
genMarkerNode() {
const node = document.createElement('div');
node.style.width = '12px';
node.style.height = '12px';
node.style.borderRadius = '50%';
node.style.background = this.option.secondaryColor;
node.style.boxSizing = 'border-box';
node.style.border = `2px solid ${this.option.mainColor}`;
return node;
}
onClickMap = e => {
const marker = new mapbox.Marker({
element: this.genMarkerNode(),
draggable: true,
}).setLngLat(e.lngLat).addTo(this.map);
const { lng, lat } = e.lngLat;
this.coordinates.push([lng, lat]);
this.updateLabels();
this.draw();
this.markers.push(marker);
this.map.fire('ruler.change', { coordinates: this.coordinates });
marker.on('drag', () => {
const index = this.markers.indexOf(marker);
const { lng: newLng, lat: newLat } = marker.getLngLat();
this.coordinates[index] = [newLng, newLat];
this.updateLabels();
this.draw();
});
marker.on('dragend', () => {
this.map.fire('ruler.change', { coordinates: this.coordinates });
});
};
// 更新测量结果值
updateLabels() {
const { coordinates } = this;
let sum = 0;
this.labels = coordinates.map((coordinate, index) => {
if (index === 0) return labelFormat(0);
sum += turf.distance(coordinates[index - 1], coordinates[index], { units: 'meters' });
return labelFormat(sum);
});
}
}
export default Ruler;