Add watermarks to WebGIS tile maps (vector tiles, raster tiles)

Watermark technology

Watermark can provide strong evidence of the ownership of products receiving copyright information, and can monitor the spread of protected data, authenticate and control illegal copies, etc. It is also needed in today’s popular online maps Watermark technology protects map data. This article will introduce how to add watermarks to tile maps, including raster tiles and vector tiles.

During the exploration process, we referred to “Front-end Watermark Generation Scheme (Web Page Watermark + Image Watermark)” and “Openlayer Slice Layer Adding Watermark”

Implementation plan

Option 1: Pure front-end implementation

By setting a watermark to the HTML tag, add canvas at the front of the current view. The code is as follows:

<!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="//i2.wp.com/cdn.bootcdn.net/ajax/libs/openlayers/4.6.5/ol.js"></script>
  <script>
    (function () {<!-- -->
      // canvas implements watermark
      function __canvasWM({<!-- -->
        // Use ES6's function default value method to set the default value of the parameter
        // For details, see 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 = 'for reference only',
        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;
    })();
    //Call watermark
    __canvasWM({<!-- -->
      content: 'watermark'
    })
  </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>

The effect is as follows:

Option 2: Send slices back to the front end for secondary processing (applicable to both raster tiles and vector tiles)

This solution is to use tile slicing to create a separate watermark, and use the callback function of the tile URL to return the base64 watermark image. The watermark image can be made in advance and converted to base64 or Canvas can be used to dynamically brake the watermark. Things to note The width and height of the watermark image need to be 512X512. The watermark layer addition of Openlayer and Mapbox is implemented here.

Openlayer implements watermark layer

The function used by Openlayer is tileUrlFunction, the corresponding official document explains

code show as below:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Openlayer map watermark</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="//i2.wp.com/cdn.bootcdn.net/ajax/libs/openlayers/7.5.2/dist/ol.min.js"></script>
    <script>
        //Create 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'));
        }

        //Watermark tile layer
        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];
                    //Create Canvas
                    const context = createCanvasContext2D(tileSize[0], tileSize[0]);
                    //Fill style
                    context.fillStyle = 'rgba(184, 184, 184, 0.8)';
                    //Text position
                    context.textAlign = 'center';
                    context.textBaseline = 'middle';
                    //Text font size
                    context.font = '48px microsoft yahei';
                    //slope
                    context.rotate(Math.PI / 180 * 30);
                    //Text content
                    context.fillText(`for reference only`, half, half);
                    //return 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()
                }),
                //Add watermark layer
                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>

final effect:

MapBox implements watermark layer

The function used by Mapbox is transformRequest, and the corresponding official document explains

code show as below:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title>MapBox map watermark</title>
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
  <script src="//i2.wp.com/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 = 'your key';
    const map = new mapboxgl.Map({<!-- -->
      container: 'map',
      style: 'mapbox://styles/mapbox/streets-v11',
      //Callback
      transformRequest: function transformRequest(url, resourceType) {<!-- -->
        if (resourceType === 'Tile' & amp; & amp; url.startsWith('http://ip:watermark')) {<!-- -->
          //When the data source type is Tile and the tile address is http://ip:watermark, modify the tile URL
          return {<!-- -->
            //TODO: base64 or URL watermark image
            //TODO: The image size can be adjusted to include 256X256, 512X512 and other square images.
            url: ''
          };
        }
      }
    });
    //TODO: Map loading success event
    map.on('load', () => {<!-- -->
      //TODO: Add watermark data source
      map.addSource('watermark', {<!-- -->
        type: 'raster',
        //It doesn't matter whether a tile address can be loaded or not.
        tiles: ['http://ip:watermark/{x}/{y}/{z}.png'],
        //TODO: Set the tile size 256 512 optional, you can adjust the density
        tileSize: 512,
        minzoom: 0,
        maxzoom: 24
      });
      //TODO: Add watermark layer
      map.addLayer({<!-- -->
        id: "watermark",
        type: "raster",
        source: "watermark",
        //TODO: Set the minimum display level
        minzoom: 0,
        //TODO: Set the maximum display level
        maxzoom: 24,
        //TODO: Set transparency
        paint: {<!-- --> "raster-opacity": 0.5 },
        //TODO: Set the vertical index value of the layer to always stay on top
        zIndex: 999999
      });
    });
    map.addControl(new mapboxgl.NavigationControl());
  </script>
</body>

</html>

final effect:

Other instructions:

1. MapBox can also be implemented through json style files,

The watermark image needs to be made into a script sprite. You can refer to this article to generate a Mapbox sprite, and then create a transparent background setting icon. The size of the same watermark image is 512X512. The style.json example is as follows :

{<!-- -->
  "version": 8,
  "name": "Watermark Style",
  "metadata": {<!-- -->"maputnik:renderer": "mbgljs"},
  "sources": {<!-- -->},
  "sprite": "",
  "glyphs": " ",
  "layers": [
    //Other layers
    ...
    //watermark layer
    {<!-- -->
      "id": "watermark",
      "type": "background",
      "paint": {<!-- -->
        "background-color": "rgba(255, 255, 255, 1)",
        "background-pattern": "watermark",
        "background-opacity": 0.5
      }
    }
  ],
  "id": "Watermark"
}

The effect is as follows:

2. Watermark density control

Watermark image size: Because the map tile size is 512X512, images of different sizes will have an impact. We recommend 512X512, but you can also try watermark images of other sizes such as 256X256 or 1024X1024.

Watermark proportion: The proportion of the text or logo in the middle of the watermark image to the entire image will also affect the watermark density.

Map zoom level: Since the watermark is loaded on tiles, the smaller the map level, the denser the tiles, resulting in a denser watermark display. The watermark density can be adjusted by controlling the maximum zoom ratio of the watermark layer.

Option 3: Data layer implementation (mainly for grid tiles)

When generating raster slices, adding watermarks to the raster slices is actually a superimposition process of pictures and watermark pictures. You can write a batch program after the slicing is completed to perform watermark superposition processing on all slice pictures. This has not been implemented yet. .