博主是一名webgis小白。学习webgis到发布这边文章开始只有一周时间,实现这个效果走了很多的弯路。博主研究了官网文档后,官方文档并没有对于图片有这种效果。下方是效果展示:
作者微信: BH_JJ89757 觉得不错可以添加我好有。可以一起沟通!
<style scoped>
.maps-container{
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
position: relative;
#maps{
width: 100vw;
height: 100vh;
position: absolute;
}
.fabric-container{
width: 100vw;
height: 100vh;
position: absolute;
}
}
.input-container{
position: absolute;
top: 0px;
left: 0px;
width: 150px;
height: 30px;
color: #ffffff;
padding-left: 5px;
.input{
border-radius: 10px;
outline: none;
width: 100%;
height: 100%;
border: 2px solid #535bf2;
background-color: #999ff3;
color: #ffffff;
letter-spacing: 3px;
text-align: center;
}
}
</style>
<template>
<div class="maps-container">
<!-- 地图视图 -->
<div id="maps" ref="maps">
</div>
<!-- 编辑层 -->
<div class="fabric-container" :style="{zIndex: state.fabric_z_index}">
<canvas id="fabric"></canvas>
</div>
<!-- 输入框 -->
<div class="input-container" :style="{
zIndex: state.fabric_z_index,
left: (state._now_feature.start_left - state._now_feature.width / 2) - 10 + 'px',
top: (state._now_feature.start_top + state._now_feature.height / 2) + 10 + 'px',
width: state._now_feature.width + 'px',
}">
<input class="input" v-model="state.inputValue" type="text" placeholder=""/>
</div>
</div>
</template>
<script setup>
/*******************************************/
/********** openLayers + FabricJs **********/
/*******************************************/
import html2canvas from "html2canvas";
import {onMounted, reactive, ref} from "vue";
import { fabric } from 'fabric';
import View from "ol/View";
import Map from "ol/Map";
import TileLayer from "ol/layer/Tile";
import {XYZ} from "ol/source";
import VectorSource from "ol/source/Vector";
import {Feature} from "ol";
import {Fill, Icon, Stroke, Style, Text} from "ol/style";
import VectorLayer from "ol/layer/Vector";
import {Point} from "ol/geom";
import {toLonLat} from "ol/proj";
const maps = ref(null) // 获取元素Dom
const state = reactive({
MapsViewOption: { // 地图视图参数
center: [116.39702518856394, 39.918590567855425], // 默认视图中心点经纬度
projection: "EPSG:4326", // EPSG码(默认情况下是:EPSG:3857)
zoom: 16, // 默认视图层级
maxZoom: 17, // 最大地图层级
minZoom: 1, // 最晓地图层级
},
mapsXYZ: "https://map.geoq.cn/ArcGIS/rest/services/ChinaOnlineStreetPurplishBlue/MapServer/tile/{z}/{y}/{x}", // 地图XYZ 免费的XYZ瓦片地图地址
fabric_z_index: -1, // 默认编辑层 的定位层级为 -1 在地图之下
_now_feature: {}, // 当前编辑的是哪一个feature
})
// 准备的数据
let layers = [ // 编辑层上元素 数据
{
url: 'http://pic.soutu123.com/element_origin_min_pic/16/10/04/0157f29624c2501.jpg%21/fw/700/quality/90/unsharp/true/compress/true',
start_left: 100, // 起始坐标 left
start_top: 50, // 起始坐标 top
width: 83, // 图片宽度
height: 83, // 图片高度
originWidth: 650, // 图片原始高度
originHeight: 425, // 图片原始宽度
angle: 0, // 图片旋转角度
img_feature: {}, // 图片特征对象(fabric的feature对象)
img_vector_source: {}, // 图片保存的矢量图层对象(vector)
inputValue: '' // 图片 // 输入框距离(这个值目前只是绑定内容 后续开发自己发掘)
},
{
url: 'https://img2.baidu.com/it/u=2946301796,104274799&fm=253&fmt=auto&app=138&f=PNG?w=500&h=500',
start_left: 500,
start_top: 500,
width: 83,
height: 83,
originWidth: 500,
originHeight: 500,
angle: 0,
img_feature: {},
img_vector_source: {},
inputValue: ''
}
]
let mapsObj = null // 实例化 maps 对象
let fabricObj = null // 实例化 Fabric 对象
let coordinate = [] // 鼠标在地图中的经纬度
onMounted( () => {
// 初始化地图
mapsObj = init_maps()
// 创建FabricJs用于地图编辑层
fabricObj = init_fabric()
// 设置编辑层的宽高和地图视图大小一致
set_Fabric_width_height()
// 添加地图瓦片至Layer
mapsObj.addLayer(create_maps_layer_XYZ())
// 通过监听地图加载第一次加载 使用once 使用on事件会多次触发 然后添加对应 layer 至地图上
mapsObj.once('rendercomplete', ()=>{
layers.forEach((layersItem, layersIndex) => {
add_image_static_to_map(layersItem, layersIndex)
})
})
// 给地图添加点击事件,
mapsObj.on('click', function (evt) {
// 在点击位置检查特征,返回第一个命中的特征
let _now_feature = mapsObj.forEachFeatureAtPixel(evt.pixel, function(feature, layer) {
return feature;
});
if(_now_feature){
for (let i = 0; i < layers.length; i++) {
let layerItem = layers[i]
if(layers[i].img_feature.ol_uid === _now_feature.ol_uid){
state._now_feature = layerItem
let imgPoint = layers[i].img_feature.getGeometry().getCoordinates();
// 动态获取图片 距离地图视图的left 和 top 值
let _imgPoint_position = mapsObj.getPixelFromCoordinate([imgPoint[0],imgPoint[1]])
// let _img_feature_position = [_imgPoint_position[0]- layerItem.width / 2,_imgPoint_position[1]- layerItem.height / 2]
layerItem.start_left = _imgPoint_position[0]
layerItem.start_top = _imgPoint_position[1]
// 将图片添加到 fabric 编辑层
add_image_static_to_fabric(layerItem)
}
}
}
});
mapsObj.on('pointermove', function (event) {
coordinate = toLonLat(event.coordinate); // 将地图坐标转换为经纬度坐标
const hit = mapsObj.hasFeatureAtPixel(event.pixel);
mapsObj.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
// 监听地图层级变化
// mapsObj.getView().on('change:resolution', function(){
// for (let i = 0; i < layers.length; i++) {
// //获取图标的样式对象
// let style = layers[i].img_feature.getStyle();
// console.log(this.getZoom())
// // 重新设置图标的缩放率,基于层级10来做缩放
// style.getImage().setScale(this.getZoom() / 17);
//
// layers[i].img_feature.setStyle(style);
// }
// })
})
/*
* 初始化 openLayers 地图 视图层
* */
const init_maps = () => {
return new Map({
layers: [],
target: maps.value,
view: new View(state.MapsViewOption)
});
}
/*
* 初始化 Fabric 编辑层
* */
const init_fabric = ()=>{
return new fabric.Canvas('fabric', {
isDrawingMode: false, //设置是否可以绘制
})
}
/*
* 创建地图瓦片Layer
* */
const create_maps_layer_XYZ = ()=>{
return new TileLayer({
title: "地图瓦片",
source: new XYZ({
url: state.mapsXYZ,
}),
})
}
/*
* 设置编辑层操作大小和地图一致
* */
const set_Fabric_width_height = ()=>{
const {width, height} = maps.value.getBoundingClientRect()
fabricObj.setDimensions({width, height})
}
/*
* 添加图片到地图上
* */
const add_image_static_to_map = (layerItem) => {
// console.log('添加到地图层', layerItem)
// 创建一个空的矢量源
let vectorSource = new VectorSource();
// 创建一个特征并从图片创建一个样式
let feature = new Feature({
// population: 4000,
// rainfall: 500,
});
feature.setStyle(
new Style({
image: new Icon(({
src: layerItem.url,
width: layerItem.width,
height: layerItem.height,
rotation: Math.PI / 180 * layerItem.angle, // 设置图片的旋转角度,用弧度表示
})),
text: new Text({
text: '',
font: '20px Calibri,sans-serif',
fill: new Fill({
color: 'white',
}),
stroke: new Stroke({
color: 'white',
width: 2,
}),
offsetY: (70 < layerItem.angle < 90) || (240 < layerItem.angle < 280) ? (layerItem.height / 2) + 40 : (layerItem.height / 2) + 40,
offsetX: layerItem.width - layerItem.width
}),
}));
console.log(layerItem.angle)
// 转换为地图坐标
let mapCoordinate = mapsObj.getCoordinateFromPixel([layerItem.start_left,layerItem.start_top])
// 添加地理位置
feature.setGeometry(new Point(mapCoordinate));
vectorSource.addFeature(feature);
// 创建矢量图层
let vectorLayer = new VectorLayer({
source: vectorSource,
});
// 添加图层到地图
mapsObj.addLayer(vectorLayer);
console.log('图片被保存中心点位置是', layerItem.start_left,layerItem.start_top)
// 保存这个特征
layerItem.img_feature = feature
layerItem.img_vector_layer = vectorLayer
if(state.fabric_z_index !== -1){
state.fabric_z_index = -1
}
}
/*
* 添加到编辑层
* */
const add_image_static_to_fabric = (layerItem)=>{
const { img_feature, img_vector_source, start_left, start_top, width, height, url, originWidth, originHeight, angle } = layerItem
// console.log('添加到编辑层', layerItem)
console.log('到编辑层获取的中心点位置是', layerItem.start_left,layerItem.start_top)
fabric.Image.fromURL(url, (fabricImg) => {
// 初始化图片的宽度、高度、角度
fabricImg.set({
scaleX: width / originWidth,
scaleY: height / originHeight,
left: start_left,
top: start_top,
originX: 'center',
originY: 'center',
angle: angle
})
// 初始化输入框参数
state._now_feature.start_top = fabricImg.top + Math.abs(Math.sin(angle * Math.PI / 90) * (fabricImg.height * fabricImg.scaleY / 2) + 50)
// 初始化编辑参数
let _left_point = 0
let _top_point = 0
let _angle_point = angle
// 监听图片在fabric(编辑层)的【添加】
fabricImg.on('added',(e)=>{
console.log('监听图片在fabric(编辑层)的【添加】')
// 在地图上移除掉这个图片
mapsObj.removeLayer(layerItem.img_vector_layer)
// 打开编辑层
state.fabric_z_index = 1
})
// 监听图片在fabric(编辑层)的【选中】
fabricImg.on('selected',(e)=>{
console.log('监听图片在fabric(编辑层)的【选中】')
})
// 监听图片在fabric(编辑层)的【取消选中】
fabricImg.on('deselected',(e)=>{
console.log('监听图片在fabric(编辑层)的【取消选中】')
// 更新对应layers
layerItem.width = fabricImg.width * fabricImg.scaleX
layerItem.height = fabricImg.height * fabricImg.scaleY
layerItem.start_left = _left_point || fabricImg.left
layerItem.start_top = _top_point || fabricImg.top
layerItem.angle = _angle_point
state._now_feature = layerItem
// 编辑层移除元素
fabricObj.remove(fabricImg)
// 重新渲染画布
fabricObj.renderAll()
// 将编辑后图片传给 地图 (添加至地图)
add_image_static_to_map(layerItem)
})
// 监听图片在fabric(编辑层)的【移动】
fabricImg.on('moving',(e)=>{
console.log('监听图片在fabric(编辑层)的【移动】')
// 计算点要素移动的位置
_left_point = fabricImg.left
_top_point = fabricImg.top
_angle_point = fabricImg.angle
state._now_feature.width = fabricImg.width * fabricImg.scaleX
state._now_feature.height = fabricImg.height * fabricImg.scaleY
state._now_feature.start_left = _left_point || fabricImg.left
state._now_feature.start_top = _top_point || fabricImg.top
state._now_feature.angle = _angle_point
state._now_feature.start_top = fabricImg.top + Math.abs(Math.sin(_angle_point * Math.PI / 90) * (fabricImg.height * fabricImg.scaleY / 2) + 50)
})
// 监听图片在fabric(编辑层)的【旋转】
fabricImg.on('rotating',(e)=>{
console.log('监听图片在fabric(编辑层)的【旋转】')
// 记录点要素旋转的角度
_angle_point = fabricImg.angle
state._now_feature.angle = _angle_point
state._now_feature.start_top = fabricImg.top + Math.abs(Math.sin(_angle_point * Math.PI / 90) * (fabricImg.height * fabricImg.scaleY / 2) + 50)
})
// 监听图片在fabric(编辑层)的【缩放、放大】
fabricImg.on('scaling',(e)=>{
console.log('监听图片在fabric(编辑层)的【缩放、放大】')
_left_point = fabricImg.left
_top_point = fabricImg.top
_angle_point = fabricImg.angle
state._now_feature.width = fabricImg.width * fabricImg.scaleX
state._now_feature.height = fabricImg.height * fabricImg.scaleY
state._now_feature.start_left = _left_point || fabricImg.left
state._now_feature.start_top = _top_point || fabricImg.top
state._now_feature.angle = _angle_point
state._now_feature.start_top = fabricImg.top + Math.abs(Math.sin(_angle_point * Math.PI / 90) * (fabricImg.height * fabricImg.scaleY / 2) + 50)
})
fabricObj.add(fabricImg)
},{ left: start_left,top: start_top })
}
</script>