Mapboxgl Internet map correction plugin (1) there is a problem when the map rotation tile dislocation.

Instead of fighting mapBoxGL’s transformation matrix, I used mapBoxGL’s custom layers and rewrote the tile loading method to correct the map’s deviation.

The following I beat strange upgrade of the heart of the road to share, perhaps you also have inspiration.

The article involves some webGL knowledge details. For those who have not been exposed to WebGL, you can refer to the ebook “WebGL Programming Guide” recommended last time. This time, a Github library containing all the examples in the book will be very helpful.

Book to back

In the study of migration matrix, it is found that there is no deviation problem with the grid tiles of the map of heaven and earth, because the map of heaven and earth is the 2000 coordinate of the earth, which can be directly used on the WGS84 coordinate map with almost no error.

After trying, I feel that I can, but the color match is a little ugly, so it can be used as a guarantee scheme first, and the correction of Gaode tiles should be further studied.

After reading “WebGL Programming Guide”, I always wanted to write a book note, but I thought it was too boring to write notes alone, so I thought I could do something with the map.

Mapboxgl supports the extension of WebGL through the custom layer interface, which has the advantage of encapsulating the complex transformation matrix, using the familiar Web Mercator coordinates, and providing the interface for latitude and longitude coordinates and Web Mercator coordinates conversion.

When I looked at the official example of MapBoxGL, I was inspired to use this interface to write a program to load grid tiles, so that it can bypass the complex framework of MapBoxGL, it is easier to implement the correction of tiles, and it is easier to solve problems, and have a better sense of control over the whole.

Technical Route analysis:

Use this idea to achieve correction, to solve two major problems, one is how to use WebGL to achieve the function of display tiles, the other is how to calculate the display position of tiles on the screen.

How to display tiles in WebGL

In WebGL, the graph is based on triangles. To draw square tiles, you need to use two triangles to form a square, and then attach pictures to the square to realize the display of map tiles. In this process, the image is called texture and the texture is called texture mapping. The effect is as follows (the position of the image is arbitrary) :

Two things to note here:

1. Attention should be paid to the cross-domain problem of the image, which needs to be solved by setting the cross-domain properties of the image.

2, pay attention to the order of vertex coordinates, the correct order is: left up, left down, right up, right down, otherwise the picture will be like wearing clothes, all kinds of wear backwards, backwards and backwards, left and right

The core code is as follows:

    var picLoad = false;
    var tileLayer = {
        id: 'tileLayer'.type: 'custom'.// called when adding layers
        onAdd: function (map, gl) {
            var vertexSource = "" +
                "uniform mat4 u_matrix;" +
                "attribute vec2 a_pos;" +
                "attribute vec2 a_TextCoord;" +
                "varying vec2 v_TextCoord;" +
                "void main() {" +
                "Gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0); +
                " v_TextCoord = a_TextCoord;" +
                "}";

            var fragmentSource = "" +
                "precision mediump float;" +
                "uniform sampler2D u_Sampler; " +
                "varying vec2 v_TextCoord; " +
                "void main() {" +
                " gl_FragColor = texture2D(u_Sampler, v_TextCoord);" +
                "}";

            // Initialize the vertex shader
            var vertexShader = gl.createShader(gl.VERTEX_SHADER);
            gl.shaderSource(vertexShader, vertexSource);
            gl.compileShader(vertexShader);
            // Initializes the slice shader
            var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
            gl.shaderSource(fragmentShader, fragmentSource);
            gl.compileShader(fragmentShader);
            // Initialize the shader program
            var program = this.program = gl.createProgram();
            gl.attachShader(this.program, vertexShader);
            gl.attachShader(this.program, fragmentShader);
            gl.linkProgram(this.program);

            
            // Get the vertex position variable
            var a_Pos = gl.getAttribLocation(this.program, "a_pos");
            var a_TextCoord = gl.getAttribLocation(this.program, 'a_TextCoord');
            // Set the graph vertex coordinates
            var leftTop = mapboxgl.MercatorCoordinate.fromLngLat({lng: 110.lat: 40});
            var rightTop = mapboxgl.MercatorCoordinate.fromLngLat({lng: 120.lat: 40});
            var leftBottom = mapboxgl.MercatorCoordinate.fromLngLat({lng: 110.lat: 30});
            var rightBottom = mapboxgl.MercatorCoordinate.fromLngLat({lng: 120.lat: 30});
            // Vertex coordinates are placed in the WebGL buffer
            var attrData = new Float32Array([
                leftTop.x, leftTop.y, 0.0.1.0,
                leftBottom.x, leftBottom.y, 0.0.0.0,
                rightTop.x, rightTop.y, 1.0.1.0,
                rightBottom.x, rightBottom.y, 1.0.0.0
            ])
            var FSIZE = attrData.BYTES_PER_ELEMENT;
            this.buffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
            gl.bufferData(gl.ARRAY_BUFFER, attrData, gl.STATIC_DRAW);
            // Sets the rule for getting vertex data from the buffer
            gl.vertexAttribPointer(a_Pos, 2, gl.FLOAT, false, FSIZE * 4.0);
            gl.vertexAttribPointer(a_TextCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
            // Activate the vertex data buffer
            gl.enableVertexAttribArray(a_Pos);
            gl.enableVertexAttribArray(a_TextCoord);

            var _this = this;
            var img = this.img = new Image();
            img.onload = () = > {
                 // Create a texture object
                 _this.texture = gl.createTexture();
                // Bind the texture object to target
                gl.bindTexture(gl.TEXTURE_2D, _this.texture);
                // Invert the texture on the Y-axis
                gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
                // Configure the texture image
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, this.img);

                picLoad = true;
            };
            img.crossOrigin = true;	// Set to allow cross-domain
            img.src = "http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x=843&y=386&z=10";
        },

        // Render, this method is called several times when the map interface changes (every frame of the change)
        render: function (gl, matrix) {
            if(picLoad){
                // Apply the shader
                // It must be written here, not in onAdd, otherwise the shader in GL may not be written above, and the following variables will not be obtained
                gl.useProgram(this.program);

                // Bind the texture object to target
                gl.bindTexture(gl.TEXTURE_2D, this.texture);
                // Turn on texture unit 0
                gl.activeTexture(gl.TEXTURE0);
                // Configure texture parameters
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT);
                // Get the location of the texture
                var u_Sampler = gl.getUniformLocation(this.program, 'u_Sampler');
                // Pass texture 0 to the shader
                gl.uniform1i(u_Sampler, 0);                

                // Assign the position change matrix
                gl.uniformMatrix4fv(gl.getUniformLocation(this.program, "u_matrix"), false, matrix);
                // Draw a graph
                gl.drawArrays(gl.TRIANGLE_STRIP, 0.4); }}}; map.on('load'.function () {
        map.addLayer(tileLayer);
    });

Copy the code

This seems to be a simple problem, but students who are not familiar with WebGL may go astray. In my own research, I encountered the following problems:

First question:

Custom layers must have onAdd and Render methods. OnAdd is called once when loading layers, and Render is called several times when panting, zooming, or rotating maps to achieve smooth transitions.

Which webGL code should go in onAdd and which should go in Render?

Here’s how webglFundamentals explains it, where the onAdd method is the initialization phase and the Render method is the render phase.

Second question:

Are vertex coordinates one tile with one buffer, or are they all in one buffer and then define rules to take them?

The answer is, a tile is an independent graphic, a graph corresponding to a set of own vertex coordinates, coordinates the back can be associated with rendering attributes, such as color, texture coordinates, the vertex coordinates of multiple graphics is best not to put together, it is recommended to use multiple buffer object storage graph vertex coordinates respectively, this will become more clear, open is more simple.

Third question:

How do I use multiple buffers?

Webgl is process-oriented, so I’m used to using object-oriented development language. At first, I felt a little uncomfortable with it, but later I gradually got familiar with it.

We can think of WebGL as an old mechanical printing press. It prints according to templates, and can only use one template at a time. If you want to print multiple patterns, you need to prepare multiple templates of different patterns, and then change the template constantly during printing.

The combination of shaders, buffer objects, and texture objects in WebGL is like this template, and the template and all of them contain parameters for drawing graphics. When you change a template, you’re changing shaders, buffer objects, and texture objects. The difference is that on a computer, you’re changing shaders, buffer objects, and texture objects in a fraction of a second.

In actual work, WebGL keeps changing templates and printing, and changing templates and printing again, just like the printing press above, until all images are drawn. The whole process is a matter of a moment.

In WebGL, there is only one “printing machine”, but you can create as many “templates” as possible, depending on the performance of your computer.

All we need to do is create a “template” for each tile, and then dynamically switch these “templates” when drawing.

After figuring out the above three problems, I successfully loaded two tiles. Effect:

How do I calculate the position of the tile on the screen

The core is to use the latitude and longitude and tile numbering algorithm mentioned in the previous article

The principle is: first obtain the latitude and longitude of the four corners of the current display range, and then calculate the number of tiles corresponding to the four corners according to the reciprocal algorithm, so that the current map range of all tiles can be counted out.

It then iterates through all tile numbers in the current range.

Traversal: According to the reciprocal algorithm, the traversal number of each tile is converted to the latitude and longitude of the upper left corner of the tile, and then the four vertex coordinates of the tile are composed by using the upper left latitude and longitude of the three adjacent tiles on the right, lower and lower right.

In this step, the correction algorithm of vertex coordinates is added to realize the correction of tiles.

Finally to monitor the event of map change, map translation, zoom, rotation to repeat the above calculation, update tiles.

There is a problem here: after correction, there is also a blank edge in the previous article. So the above algorithm is optimized, after obtaining the latitude and longitude coordinates of the four angles of the current display range, the four coordinates are also corrected, so the problem is solved.

Now tile map frame up, also can browse to view tile map, this moment is really a little bit excited about it

However, there is still a gap between the final desired effect and many details need to be optimized

Details of the optimization

1. Cache tiles

Put the requested tiles into the save variable, so that the requested tiles can avoid repeated requests, display faster, and experience better.

2. Cache grid longitude and latitude

The latitude and longitude of the tile grid are uniformly calculated and cached to avoid double-counting every time.

3. Tiles are loaded from the middle to the periphery

Now the order is from left to right, it’s kind of like a swipe screen, you need to number the tiles in order, so that the tiles near the middle are loaded first, and those near the edges are loaded later.

4, individual tiles do not show the problem

The render method is executed dozens of times each time the map scope is changed to achieve a smooth effect, which takes about 1 second.

If the tile is not loaded during this time, it will be left behind and will not be displayed.

It is necessary to record the matrix transformation matrix when the render method is executed for the last time, and actively call the Render method to draw after the tile loading is completed.

5. The problem of white background annotation on image map

When loading the image image, the image and the note are separated and need to be displayed superimposed. The note layer is transparent where there is no text.

But when added together, the note layer turns out to be opaque white where it should be transparent

First, because there is A problem in the configuration of reading texture pixel data, gl.rGBA should be used. If gl.RGB is used without transparency A, transparency information will be lost, resulting in opacity.

Second, since alpha blending is not enabled on WebGL before rendering (alpha here means transparency), this option must be enabled in WebGL if transparency is to be implemented.

Effect after solution:

6. The problem of white background annotation on image map still appears occasionally

After the previous modification, the frequency of the white background problem is significantly reduced, but it still occasionally occurs.

Research rules, when the annotation tile loading time is a little long will appear, after the appearance, as long as a little drag map will be normal, has been browsing the area does not have this problem.

It is speculated that image and annotation are drawn on layers. When the loading time of individual annotation tiles is long and the render method is actively called to redraw, all annotation layers will be redrawn, but the image layer will not be drawn, which may result in the dynamic mixing of the two layers.

The current solution is to not actively call the Render method to redraw the annotation layer if it is loading slowly. Because missing a small piece of note does not affect the overall situation, and it will automatically return to normal in the next operation.

Map jitter problem

After some optimization, the map is now corrected, and the rotation is no longer misplaced. I thought the program was perfect, but when I stacked the real data of the project, I found a very serious problem, which was the jitter problem of the custom layer when it was at large scale.

It was a problem that I noticed at first and didn’t pay much attention to, but when I added up the business data, it didn’t work at all. It was like being on a tractor down a country road, bump, bump, bump, bump, bump

At first I thought it was a bug in mapBoxGL that caused the tile number and latitude and longitude transpose.

Help MapBoxGL to find problems, and finally locate on the matrix transformation matrix of render method. This parameter is transmitted by MapBoxGL, which is used to transform Web Mercator coordinates into WebGL coordinates, and scale and rotate tiles.

When only a small translation of the map is carried out, the map will move, but the Matrix matrix will not change, the matrix matrix will not change, the custom layer will not change, the matrix matrix will change when the translation range of the map increases.

If you look at the mapBoxgl source code, the custom layer and base image are not using a transformation matrix, so only the custom layer is problematic.

The latest version of mapboxgl, v2.3.1, was also tried with this problem.

Alas! I thought the correction of this thing to turn over, so it seems to be a while more research.

Inspiration, thought, feeling

Some inspiration came from using custom layers. In the previous article, the correction was written in the transformation matrix, which caused the tiles to misplace when the map was rotated.

In this paper, the correction is to correct the web Mercator coordinates of the A_pos variable, and there is no dislocation in the rotation.

In this way, in the last article, the a_pos variable was also corrected so that the map would not be misaligned when rotated. It’s worth a try.

Therefore, the next two ideas: first, study how to improve the precision of the custom layer transformation matrix, so that it no longer shake. Study how to correct the a_pos variable in mapBoxGL source code.

Finally, let’s talk about the feeling of using mapBoxGL custom layer. Using MapBoxGL custom layer + WebGL extension, I feel that I have opened another window into the GIS world. I can realize all kinds of cool and high level functions, and feel infinite possibilities.

Code, examples

Example: online gisarmory. Xyz/blog/index…

The plug-in code: gisarmory. Xyz/blog/index…

conclusion

  1. This time, I tried to use maboxGL’s custom layer function, and wrote a program to load Internet tiles to achieve tile correction
  2. Write and load tile program to solve two major problems, one is how to use WebGL to achieve the function of display tile, two is how to calculate the display position of tile on the screen
  3. The principle of webGL display tiles is to draw a square and texture the square
  4. To calculate the position of tiles on the screen, the core is to use the tile number and latitude and longitude cross algorithm, in this process to correct the deviation of tiles
  5. Some details are optimized, such as the loading order of the tiles
  6. Finally, the gaud tiles can be corrected without dislocation during rotation
  7. But there’s a problem with this approach, mapBoxGLrenderThe accuracy of the transformation matrix passed in the method is not enough, and the tiles will shake at large scale, which should be a bug of MapBoxGL
  8. In the process of using custom layers have some inspiration, the next two ideas: first, study how to improve the precision of the transformation matrix of custom layers. How to study the mapBoxGL source codea_posVariables are corrected.
  9. The current guarantee scheme is to use tiles from Map Of Heaven and Earth, and tiles from Amap should be studied further.



The original address: gisarmory. Xyz/blog/index…

Pay attention to GIS Armory, which only gives you GIS knowledge and skills that you can’t find online.

This article is licensed under the Creative Commons Attribution – Noncommercial Use – Share alike 4.0 International License. You are welcome to reprint, use and republish the article under the name “GIS Armory” (including link: gisarmory. Xyz /blog/). It must not be used for commercial purposes, and all works based on this article must be published under the same license.