水印技术
水印能为收到版权信息产品归属提供有力的证据, 并能够监视被保护数据的传播, 真伪鉴别以及非法拷贝控制等.在现今流行的线上地图同样需要水印技术, 保护地图数据.本文将介绍如何实现瓦片地图水印添加, 包括栅格瓦片、矢量瓦片.
在探索过程中, 参考了《前端水印生成方案(网页水印+图片水印)》、《Openlayer 切片图层添加水印》
实现方案
方案一: 纯前端实现
通过给HTML的标签设置水印, 在当前视图最前添加canvas, 代码如下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>map demo</title>
<link href="https://cdn.bootcdn.net/ajax/libs/openlayers/4.6.5/ol.css" rel="stylesheet">
</head>
<style>
html,
body,
#map {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
<body>
<div id="map"></div>
<script src="https://cdn.bootcdn.net/ajax/libs/openlayers/4.6.5/ol.js"></script>
<script>
(function () {
// canvas 实现 watermark
function __canvasWM({
// 使用 ES6 的函数默认值方式设置参数的默认取值
// 具体参见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Default_parameters
container = document.body,
width = '256px',
height = '256px',
textAlign = 'center',
textBaseline = 'middle',
font = "20px microsoft yahei",
fillStyle = 'rgba(184, 184, 184, 0.8)',
content = '仅供参考',
rotate = '30',
zIndex = 1000
} = {}) {
var args = arguments[0];
var canvas = document.createElement('canvas');
canvas.setAttribute('width', width);
canvas.setAttribute('height', height);
var ctx = canvas.getContext("2d");
ctx.textAlign = textAlign;
ctx.textBaseline = textBaseline;
ctx.font = font;
ctx.fillStyle = fillStyle;
ctx.rotate(Math.PI / 180 * rotate);
ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2);
var base64Url = canvas.toDataURL();
console.log(base64Url);
const watermarkDiv = document.createElement("div");
watermarkDiv.setAttribute('style', `
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
z-index:${zIndex};
pointer-events:none;
background-repeat:repeat;
background-image:url('${base64Url}')`);
container.style.position = 'relative';
container.insertBefore(watermarkDiv, container.firstChild);
};
window.__canvasWM = __canvasWM;
})();
//调用水印
__canvasWM({
content: '水印'
})
</script>
<script>
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
view: new ol.View({
zoom: 4,
center: ol.proj.transform([110, 39], "EPSG:4326", "EPSG:3857")
})
});
</script>
</body>
</html>
效果如下:
方案二: 切片传回前端进行二次处理(栅格瓦片、矢量瓦片均适用)
此方案是使用 tile 切片的方式, 单独创建一个水印, 运用瓦片URL的回调函数返回base64的水印图片实现, 其中水印图片可以提前制作好转为base64或者使用Canvas动态制动水印, 需要注意的是水印图片宽高需要为512X512.这里实现了Openlayer和Mapbox的水印图层添加.
Openlayer实现水印图层
Openlayer使用的函数是tileUrlFunction
, 相应的官方文档说明
代码如下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Openlayer地图水印</title>
<link href="https://cdn.bootcdn.net/ajax/libs/openlayers/7.5.2/ol.min.css" rel="stylesheet">
</head>
<style>
html,
body,
#map {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
<body>
<div id="map">
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/openlayers/7.5.2/dist/ol.min.js"></script>
<script>
//创建Canvas
function createCanvasContext2D(opt_width, opt_height) {
const canvas = (document.createElement('canvas'));
if (opt_width) {
canvas.width = opt_width;
}
if (opt_height) {
canvas.height = opt_height;
}
return (canvas.getContext('2d'));
}
//水印瓦片图层
var tiles = new ol.layer.Tile({
source: new ol.source.XYZ({
tileUrlFunction: function (t) {
var zoom = t[0];
var tile = {
x: t[1],
y: -(t[2] + 1)
}
var tileSize = [512, 512];
const half = tileSize[0] / 2;
const lineheight = 48;
var tileSize = [512, 512];
//创建Canvas
const context = createCanvasContext2D(tileSize[0], tileSize[0]);
//填充样式
context.fillStyle = 'rgba(184, 184, 184, 0.8)';
//文字位置
context.textAlign = 'center';
context.textBaseline = 'middle';
//文字字体大小
context.font = '48px microsoft yahei';
//倾斜角度
context.rotate(Math.PI / 180 * 30);
//文字内容
context.fillText(`仅供参考`, half, half);
//返回base64
return context.canvas.toDataURL();
},
extent: ol.proj.transformExtent([-180, -85, 180, 85], "EPSG:4326", "EPSG:3857")
})
});
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
}),
//添加水印图层
tiles
],
view: new ol.View({
zoom: 4,
center: ol.proj.transform([110, 39], "EPSG:4326", "EPSG:3857")//ol.proj.fromLonLat([110, 39])
})
});
</script>
</body>
</html>
最终效果:
MapBox实现水印图层
Mapbox使用的函数是transformRequest
, 相应的官方文档说明
代码如下:
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>MapBox地图加水印</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://api.mapbox.com/mapbox-gl-js/v1.11.0/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.11.0/mapbox-gl.css" rel="stylesheet" />
<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',
style: 'mapbox://styles/mapbox/streets-v11',
//回调函数
transformRequest: function transformRequest(url, resourceType) {
if (resourceType === 'Tile' && url.startsWith('http://ip:watermark')) {
//数据源类型为 Tile 且瓦片地址为 http://ip:watermark 时 对瓦片url进行修改
return {
//TODO: base64 或 URL的水印图片
//TODO: 图片大小可以调节疏密程度 256X256、512X512等其他正方形图片
url: ''
};
}
}
});
//TODO: 地图加载成功事件
map.on('load', () => {
//TODO: 添加水印数据源
map.addSource('watermark', {
type: 'raster',
//随便给一个瓦片地址能不能加载都无所谓
tiles: ['http://ip:watermark/{x}/{y}/{z}.png'],
//TODO: 设置瓦片大小 256 512可选 可以调节疏密程度
tileSize: 512,
minzoom: 0,
maxzoom: 24
});
//TODO: 添加水印图层
map.addLayer({
id: "watermark",
type: "raster",
source: "watermark",
//TODO: 设置最小显示层级
minzoom: 0,
//TODO: 设置最大显示层级
maxzoom: 24,
//TODO: 设置透明度
paint: { "raster-opacity": 0.5 },
//TODO: 设置图层垂直索引值 永远保持最上面
zIndex: 999999
});
});
map.addControl(new mapboxgl.NavigationControl());
</script>
</body>
</html>
最终效果:
其他说明:
1. MapBox还可通过json样式文件实现,
需要将水印图片制作到script精灵图里, Mapbox精灵图的生成可以参考这篇, 再创建一个透明的背景设置图标, 同样的水印图片大小为512X512, style.json实例如下:
json
{
"version": 8,
"name": "Watermark Style",
"metadata": {"maputnik:renderer": "mbgljs"},
"sources": {},
"sprite": "",
"glyphs": " ",
"layers": [
//其他图层
...
//水印图层
{
"id": "watermark",
"type": "background",
"paint": {
"background-color": "rgba(255, 255, 255, 1)",
"background-pattern": "watermark",
"background-opacity": 0.5
}
}
],
"id": "Watermark"
}
效果如下:
2. 水印密集程度控制
水印图片大小 : 因为地图瓦片大小为512X512,不同大小的图片会有影响, 推荐512X512, 也可以尝试256X256或者1024X1024等其他大小的水印图片
水印占比 : 水印图片中间文字或logo在整图中的占比情况也会对水印密度有影响
地图缩放级别: 由于水印加载是在瓦片上进行的, 地图等级越小, 瓦片越密集, 导致水印显示更密集, 可以通过控制水印图层的最大缩放比例调节水印密度
方案三: 数据层实现(主要针对栅格瓦片)
在生成栅格切片时, 对栅格切片加上水印, 实际上就是对图片和水印图片的叠加处理, 可以切片完成后写个批处理程序, 对所有切片图片进行水印叠加处理, 目前尚未具体实施.