需求背景

需要实现地图上展示一个类似于罗盘的标记,随着地图的缩放、切换、旋转等,能够在地图的中央指示出地图的方位。

系统自带的方位控件太小,在特殊业务场景下不够醒目。

技术选型

Mapbox

实现分析

  1. 官网已经有地图上展示图片矢量图层的demo,“Add a raster image to a map layer”和“Add a pattern to a polygon”。注意看前者和后者的区别,前者主要用一张图片覆盖到指定的一个多边形区域;而后者则会用图片区填充一个多边形区域,如果图片太小,矩形区域太大,则会重复叠加多张图片直到矩形区域填充满。
  2. 从地图开放的接口上来看,我们需要监听的主要地图交互事件有:拖拽、缩放、旋转。可在这三个事件完成时重新调整矢量图片的中心位置,重新绘制图层;
  3. 重新绘制图片图层时,需要注意地图的旋转角度(bearing)参数,在交互事件结束时实时获取当前地图的中心点和旋转角度,计算图片重新“贴”的位置;
  4. 通过“Add a raster image to a map layer”示例了解到,往地图上“贴”一张图片需要定义四个经纬度坐标用来确定图片的“粘贴”位置,这样在进行地图旋转或者调整俯视角度的时候就可以紧贴地图跟随地图一起变化。
  5. 当用户在交互的过程中,需要使用以地图中心点(全屏下:屏幕中心点)计算图片在地图坐标系中的四个经纬度坐标。因此我们需要将图片放在屏幕中间,同时计算出图片在屏幕上的四个屏幕坐标点,通过mapbox提供的屏幕坐标点转经纬度坐标接口进行转换。
  6. 在屏幕坐标系转地图坐标系时,需要注意图片的旋转角度。mapbox的bearing是逆时针旋转,原始的屏幕坐标点在经过指定的角度旋转之后获得一个新的屏幕坐标点,再将旋转后的坐标点通过mapbox的屏幕坐标点转经纬度坐标点就可以得到一个“贴”图的目标经纬度坐标。
  7. mapbox针对图片图层的位置更新可以通过sourcesetCoordinates(newCoordinates)进行更新,设置以后调用 map.triggerRepaint()触发地图重绘,届时功成;

代码实现

<!DOCTYPE html>
<html> <head>
<meta charset="utf-8">
<title>Add a raster image to a map layer</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.2.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.2.0/mapbox-gl.js"></script>
<style>
body {
margin: 0;
padding: 0;
} #map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>
</head> <body>
<div id="map"></div>
<script>
mapboxgl.accessToken = '你的KEY';
const map = new mapboxgl.Map({
container: 'map',
zoom: 10,
center: [103, 30],
// Choose from Mapbox's core styles, or make your own style with Mapbox Studio
style: 'mapbox://styles/mapbox/light-v11'
}); //图层的长宽尺寸, 务必和真实图片的长宽保持一致,本教程使用的是正方形,矩形同理可实现。
const imgWidth = 632;
const corners = getImgcornersAtMapCenter(map, imgWidth) map.on('load', () => {
map.addSource('compass', {
'type': 'image',
'url': './assets/823VemjWvJ.png',
'coordinates': corners
});
map.addLayer({
"id": 'radar-layer',
'type': 'raster',
'source': 'compass',
'paint': {
'raster-fade-duration': 0
}
});
}); //拖拽结束重新绘图
map.on("dragend", () => {
var source = map.getSource("compass");
var corners = getImgcornersAtMapCenter(map, imgWidth);
source.setCoordinates(corners);
map.triggerRepaint();
}); //添加zoomend事件监听器
map.on("zoomend", () => {
var source = map.getSource("compass");
var corners = getImgcornersAtMapCenter(map, imgWidth);
source.setCoordinates(corners);
map.triggerRepaint();
}); function getImgcornersAtMapCenter(map, imgWidth) {
//获取地图的bearing参数
const bearing = map.getBearing(); // 获取地图容器的宽度和高度
const mapWidth = map.getCanvas().width;
const mapHeight = map.getCanvas().height; //计算图片在地图上的显示区域的左上角和右下角的屏幕坐标
const imageTopLeft = [(mapWidth - imgWidth) / 2, (mapHeight - imgWidth) / 2];
const imageBottomRight = [imageTopLeft[0] + imgWidth, imageTopLeft[1] + imgWidth]; //计算图片旋转后的左上角和右下角
var rotatedcoordinates = rotatecoordinates(imageTopLeft, imageBottomRight, 0 - bearing) // 将屏幕坐标转换为经纬度坐标
const topLeftCoordinates = map.unproject(rotatedcoordinates[0]);
const bottomRightcoordinates = map.unproject(rotatedcoordinates[1]); return new Array(
[topLeftCoordinates.lng, topLeftCoordinates.lat], // Northwest corner
[bottomRightcoordinates.lng, topLeftCoordinates.lat], // Southwest corner
[bottomRightcoordinates.lng, bottomRightcoordinates.lat], //// Southeast corner
[topLeftCoordinates.lng, bottomRightcoordinates.lat] //Northeast corner
)
} function rotatecoordinates(topLeft, bottomRight, bearing) {
const center = [
(topLeft[0] + bottomRight[0]) / 2,
(topLeft[1] + bottomRight[1]) / 2
];
//计算左上角相对于中心点的偏移
const offsetX1 = topLeft[0] - center[0];
const offsetY1 = topLeft[1] - center[1]; //计算右下角相对于中心点的偏移
const offsetX2 = bottomRight[0] - center[0];
const offsetY2 = bottomRight[1] - center[1]; const bearingRad = bearing * (Math.PI / 180);
//计算旋转后的左上角坐标
const rotatedX1 = offsetX1 * Math.cos(bearingRad) - offsetY1 * Math.sin(bearingRad);
const rotatedY1 = offsetX1 * Math.sin(bearingRad) + offsetY1 * Math.cos(bearingRad); // 计算旋转后的右下角坐标
const rotatedX2 = offsetX2 * Math.cos(bearingRad) - offsetY2 * Math.sin(bearingRad);
const rotatedY2 = offsetX2 * Math.sin(bearingRad) + offsetY2 * Math.cos(bearingRad); //将旋转后的坐标加上中心点的偏移,得到最终坐标
const rotatedTopLeft = [rotatedX1 + center[0], rotatedY1 + center[1]];
const rotatedBottomRight = [rotatedX2 + center[0], rotatedY2 + center[1]];
return new Array(rotatedTopLeft, rotatedBottomRight);
}
</script> </body> </html>

效果截图



思考问题

【问】贴图后的图片是否“遮挡”了地图本身的事件交互?

【答】可以调整image图层在地图众图层中的位置人,让它处于最底层;

其他

离线地图服务器,有需求就提issue:mapbox-offline-server

Mapbox实战项目(1)-栅格图片图层实现地图方位展示的更多相关文章

  1. arcgis api 4.x for js 自定义叠加图片图层实现地图叠加图片展示(附源码下载)

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 4.x for js:esri 官网 api,里面详细的介绍 arcgis api 4.x 各个类 ...

  2. 前后端分离之vue2.0+webpack2 实战项目 -- webpack介绍

    webpack的一点介绍 Webpack 把任何一个文件都看成一个模块,模块间可以互相依赖(require or import),webpack 的功能是把相互依赖的文件打包在一起.webpack 本 ...

  3. vue+websocket+express+mongodb实战项目(实时聊天)

    继上一个项目用vuejs仿网易云音乐(实现听歌以及搜索功能)后,发现上一个项目单纯用vue的model管理十分混乱,然后我去看了看vuex,打算做一个项目练练手,又不想做一个重复的项目,这次我就放弃颜 ...

  4. vue+websocket+express+mongodb实战项目(实时聊天)(二)

    原项目地址:[ vue+websocket+express+mongodb实战项目(实时聊天)(一)][http://blog.csdn.net/blueblueskyhua/article/deta ...

  5. FaceRank,最有趣的 TensorFlow 入门实战项目

    FaceRank,最有趣的 TensorFlow 入门实战项目 TensorFlow 从观望到入门! https://github.com/fendouai/FaceRank 最有趣? 机器学习是不是 ...

  6. .NET Core实战项目之CMS 第五章 入门篇-Dapper的快速入门看这篇就够了

    写在前面 上篇文章我们讲了如在在实际项目开发中使用Git来进行代码的版本控制,当然介绍的都是比较常用的功能.今天我再带着大家一起熟悉下一个ORM框架Dapper,实例代码的演示编写完成后我会通过Git ...

  7. .NET Core实战项目之CMS 第六章 入门篇-Vue的快速入门及其使用

    写在前面 上面文章我给大家介绍了Dapper这个ORM框架的简单使用,大伙会用了嘛!本来今天这篇文章是要讲Vue的快速入门的,原因是想在后面的文章中使用Vue进行这个CMS系统的后台管理界面的实现.但 ...

  8. .NET Core实战项目之CMS 第十三章 开发篇-在MVC项目结构介绍及应用第三方UI

    作为后端开发的我来说,前端表示真心玩不转,你如果让我微调一个位置的样式的话还行,但是让我写一个很漂亮的后台的话,真心做不到,所以我一般会选择套用一些开源UI模板来进行系统UI的设计.那如何套用呢?今天 ...

  9. .NET Core实战项目之CMS 第十五章 各层联动工作实现增删改查业务

    连着两天更新叙述性的文章大家可别以为我转行了!哈哈!今天就继续讲讲我们的.NET Core实战项目之CMS系统的教程吧!这个系列教程拖得太久了,所以今天我就以菜单部分的增删改查为例来讲述下我的项目分层 ...

  10. .NET Core实战项目之CMS 第十六章 用户登录及验证码功能实现

    前面为了方便我们只是简单实现了基本业务功能的增删改查,但是登录功能还没有实现,而登录又是系统所必须的,得益于 ASP.NET Core的可扩展性因此我们很容易实现我们的登录功能.今天我将带着大家一起来 ...

随机推荐

  1. 【K哥爬虫普法】大众点评VS百度地图,论“数据权属”对爬虫开发的罪与罚!

    我国目前并未出台专门针对网络爬虫技术的法律规范,但在司法实践中,相关判决已屡见不鲜,K哥特设了"K哥爬虫普法"专栏,本栏目通过对真实案例的分析,旨在提高广大爬虫工程师的法律意识,知 ...

  2. Windows 核心编程笔记 [2] 字符串

    1. ANSI 和 Unicode Windows 中涉及字符串的函数有两个版本 1)ANSI版本的函数会把字符串转换为Unicode形式,再从内部调用函数的Unicode版本 2)Unicode版本 ...

  3. layui之静态表格的分页及搜索功能以及前端使用XLSX导出Excel功能

    LayUI官方文档:https://layui.dev/docs/2/#introduce XLSX NPM地址:https://www.npmjs.com/package/xlsx XLSX 使用参 ...

  4. vim 从嫌弃到依赖(15)——寄存器

    在计算机里面也有寄存器,计算机中的寄存器是看得见,摸得着的实体,寄存器中存储需要经常访问的一些数据.而vim中也有寄存器的概念,vim中的寄存器是一个虚拟的概念,更像是一块专门用来存储数据的内存缓冲区 ...

  5. SqlSugar跨库查询/多库查询

    一.跨库方式1:跨库导航 (5.1.3.24) 优点1:支持跨服务器,支持跨数据库品种, 支持任何类型数据库 优点2:   超级强大的性能,能达到本库联表性能 缺点:不支持子表过滤主表 (方案有ToL ...

  6. 从嘉手札<2023-12-15>

    荒原  朔方 2023.12.15 人生实属是很愁的时间 愁到听不见一点雪花飘落的声音 愁到连随便写点文章都算得上拼尽全力 萧瑟的北风吹散了为数不多的倔强 漫天的雪花飞舞 埋葬的是那么多年走过的春秋 ...

  7. PLSQL Developer汉语设置

    PLSQLQ Developer是由Oracle公司推出的数据库开发工具,具有很好的移植性和适应性.但是当我们安装完成Oracle11g PLSQL Developer工具后发现状态栏的显示是英文,对 ...

  8. 目录 - JavaScript指南

    目   录 第一章.  JavaScript概述 第二章.  JavaScript语法基础 第三章.  JavaScript编程规范 第四章.  JavaScript工具集合 第五章.  JavaSc ...

  9. U390630 分考场题解

    题目链接:U390630 分考场 本题来自于2019年蓝桥杯国赛的题.在洛谷上也被标为了假题.原因是首先官方在需要输出浮点数的情况下,并没有开启spj,并且官方所给的数据当中,总有一两个数据以不知道到 ...

  10. ASP.NET Core分布式项目实战(集成ASP.NETCore Identity)--学习笔记

    任务24:集成ASP.NETCore Identity 之前在 Index 页面写了一个 strong 标签,需要加个判断再显示,不然为空没有错误的时候也会显示 @if (!ViewContext.M ...