Say to the map, you must be very familiar with, at ordinary times should be used baidu map, Scott, tencent map, etc., if it involves map related development needs, also have many choices, such as the previous several map will provide a set of js API, in addition also has some open source framework can be used to map, such as OpenLayers, Leaflet, etc.

So you have not thought about how these maps are rendered out, why according to a latitude and longitude can show the corresponding map, do not know it does not matter, this article will take you from zero to achieve a simple map engine, to help you understand the basic knowledge of GIS and Web map implementation principle.

Pick a latitude and longitude

First of all, we choose a longitude and latitude on Amap as the center point of our later map, open the AmAP coordinate picking tool, and randomly select a point:

The author chooses Leifeng Pagoda in Hangzhou, with latitude and longitude as follows: [120.148732,30.231006].

Tile URL analysis

Map tiles We use Autonavi’s online tiles with the following address:

https://webrd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8
Copy the code

At present, tile services of major map manufacturers follow different rules:

Google XYZ specification: Google Maps, OpenStreetMap, Autonavi, GeoQ, Map of heaven and Earth, coordinate origin in upper left corner

TMS specification: Tencent map, coordinate origin in the lower left corner

WMTS specification: the origin is in the upper left corner, the tile is not square, but rectangular, this should be the official standard

Baidu map is more maverick, projection, resolution, coordinate system are different from other manufacturers, the origin in latitude and longitude are 0 position, that is, in the middle, to the right is the positive X direction, up is the positive Y direction

The difference between tile row numbers of Google and TMS is as follows:

Although the specification is different, but the principle is consistent with the basic, is the world map projection earth into a huge square, and then carried out in accordance with the quadtree stratified cutting, such as the first floor, only a piece of tile, show the information of the world, so basic can only see a continent and the name of the sea and border, the second floor, cut into four tiles, show a little bit more information, Similarly, like a pyramid, the bottom layer has the highest resolution, the most details and the most tiles, and the top layer has the lowest resolution, the least information and the least tiles:

The number of tiles in each layer is calculated as follows:

Math.pow(math.pow (2, n), 2)// Row * column: 2^n * 2^nCopy the code

It takes 687,194,476,736 tiles for 18 floors, so the total number of tiles in a set of maps is very large.

Once the tile is cut, it is saved by row and column numbers and scaling levels, so you can see that there are three variables in the tile address: X, Y, and Z

X: row number y: column number Z: resolution, usually 0-18Copy the code

These three variables can be used to locate a tile, such as the following address, row number 109280, column number 53979, scale level 17:

https://webrd01.is.autonavi.com/appmaptile?x=109280&y=53979&z=17&lang=zh_cn&size=1&scale=1&style=8
Copy the code

The corresponding tiles are:

Introduction to coordinate system

Amap uses the GCJ-02 coordinate system, also known as the Mars coordinate system, which was released by the State Bureau of Surveying and Mapping of China in 2002. It is based on THE GPS coordinate system (WGS-84 coordinate system) after encryption, which is to add nonlinear offset, so that you can not know the true position. For the sake of national security, All domestic map service providers need to use THE GCJ-02 coordinate system.

Wgs-84 coordinate system is the international standard, EPSG number is EPSG:4326, usually GPS equipment to obtain the original latitude and longitude and foreign map manufacturers use WGS-84 coordinate system.

These two kinds of coordinate system is the geographic coordinate system and spherical coordinate, the unit for degrees, the coordinate convenient location on the earth, but is not convenient to display area and distance calculation, our impression is the map in the plane, so there was another plane coordinate system, plane coordinate system is converted from geographic coordinate system by means of projection, so also known as the projected coordinate system, Generally, the unit is meter, and there are many kinds of projection coordinate systems according to different projection methods. In the scene of Web development, the Web Mercator projection, numbered EPSG:3857, is usually used. Based on the Mercator projection, it projects the WGS-84 coordinate system into a square, which is achieved by giving up the areas above latitude 85.051129 and north. Because it is a square, a large square can be easily divided into smaller squares.

Latitude and longitude line number

In the last section, we briefly introduced the coordinate system. According to the standard of Web map, our map engine also chose to support EPSG:3857 projection, but we obtained the latitude and longitude coordinates of Mars coordinate system through Autonavi tool. Therefore, the first step is to convert the latitude and longitude coordinates into Web Mercator projection coordinates. Let’s take the Mars coordinates directly as the WGS-84 coordinates, and we’ll look at this later.

Conversion method a search on the Internet:

// The Angle turns radians
const angleToRad = (angle) = > {
    return angle * (Math.PI / 180)}// Radians turn the Angle
const radToAngle = (rad) = > {
    return rad * (180 / Math.PI)
}

// Radius of the earth
const EARTH_RAD = 6378137

/ / 4326 3857
const lngLat2Mercator = (lng, lat) = > {
    // The longitude is first turned in radians, and then since radians = arc length/radius, we get arc length = radians * radius
    let x = angleToRad(lng) * EARTH_RAD; 
    // Latitude first radians
    let rad = angleToRad(lat)
    // I'm not sure what you mean.
    let sin = Math.sin(rad)
    let y = EARTH_RAD / 2 * Math.log((1 + sin) / (1 - sin))
    return [x, y]
}

/ / 3857 4326
const mercatorTolnglat = (x, y) = > {
    let lng = radToAngle(x) / EARTH_RAD
    let lat = radToAngle((2 * Math.atan(Math.exp(y / EARTH_RAD)) - (Math.PI / 2)))
    return [lng, lat]
}
Copy the code

Coordinate with 3857, it is the unit of meters, so how to transform into a number of tiles, this involves the concept of resolution, that is, how much a pixel on the map represent the actual meters, resolution if can obtain from the map makers of document is the best, if you cannot find, can also be simple to calculate (if using calculated also not line, We know that the radius of the Earth is 6378137 meters, and the 3857 coordinate system treats the earth as a perfect sphere, so we can calculate the circumference of the Earth, and the projection is close to the earth’s equator:

So after the world map projection into the square of the length is the representative of the circumference of the earth, we also know that every level of tiles in front of the amount of calculation, and a piece of the size of the tile is usually 256 * 256 pixels, so use the earth’s circumference divided by the open floor plan after world side to know how many meters per pixel on the map represent actual:

// Circumference of the earth
const EARTH_PERIMETER = 2 * Math.PI * EARTH_RAD
// Tile pixels
const TILE_SIZE = 256

// Get the resolution at a certain level
const getResolution = (n) = > {
    const tileNums = Math.pow(2, n)
    const tileTotalPx = tileNums * TILE_SIZE
    return EARTH_PERIMETER / tileTotalPx
}
Copy the code

The circumference of the earth was calculated as 40075016.68557849, and OpenLayers was calculated as follows:

The unit of 3857 coordinate is meters, so divide the coordinate by the resolution to get the corresponding pixel coordinate, and then divide by 256 to get the row and column number of the tile:

The function is as follows:

// Calculate tile row and row numbers according to 3857 coordinates and scaling level
const getTileRowAndCol = (x, y, z) = > {
    let resolution = getResolution(z)
    let row = Math.floor(x / resolution / TILE_SIZE)
    let col = Math.floor(y / resolution / TILE_SIZE)
    return [row, col]
}
Copy the code

Next, we fixed the level as 17, so the resolution is 1.194328566955879, and the coordinates of latitude and longitude of Leifeng Tower are converted to 3857: [13374895.665697495, 3533278.205310311], using the above function to calculate the row number: [43744, 11556], we substituted these data into the address of the tile for access:

https://webrd01.is.autonavi.com/appmaptile?x=43744&y=11556&z=17&lang=zh_cn&size=1&scale=1&style=8
Copy the code

The origin of coordinates 4326 and 3857 is at the point where the equator intersects the prime meridian, nautical miles off Africa, while the origin of the tile is in the upper left corner:

It’s easier to understand the following picture:

Coordinate system origin is equivalent to 3857 in the middle of the floor plan of the world, to the right of the x axis direction, upwards as the y axis is the direction, and the origin of map tiles in the upper left corner, so we need according to the figure 】 【 green dotted line distance to calculate the distance of the orange lines 】 【, this also is very simple, the world coordinate is level green dotted line length and half of the floor plan, The vertical coordinate is half of the plane of the world minus the length of the vertical green dotted line, half of the plane of the world is half of the circumference of the earth, modify getTileRowAndCol function:

const getTileRowAndCol = (x, y, z) = > {
  x += EARTH_PERIMETER / 2     // ++
  y = EARTH_PERIMETER / 2 - y  // ++
  let resolution = getResolution(z)
  let row = Math.floor(x / resolution / TILE_SIZE)
  let col = Math.floor(y / resolution / TILE_SIZE)
  return [row, col]
}
Copy the code

The row number of the calculated tile is [109280, 53979], and the address of the tile is substituted:

https://webrd01.is.autonavi.com/appmaptile?x=109280&y=53979&z=17&lang=zh_cn&size=1&scale=1&style=8
Copy the code

The results are as follows:

Leifeng Pagoda can be seen coming out.

Tile display position calculation

We can now find tiles based on a latitude and longitude, but that’s not enough. Our goal is to display them in the browser, which requires solving two problems, one is how many tiles to load, and the other is to calculate the display position of each tile.

To render tiles we use Canvas canvas, template as follows:

<template>
  <div class="map" ref="map">
    <canvas ref="canvas"></canvas>
  </div>
</template>
Copy the code

The size of the map canvas container map is easy to get:

// Container size
let { width, height } = this.$refs.map.getBoundingClientRect()
this.width = width
this.height = height
// Set the canvas size
let canvas = this.$refs.canvas
canvas.width = width
canvas.height = height
// Get the drawing context
this.ctx = canvas.getContext('2d')
Copy the code

The center point of the map is set in the middle of the canvas. In addition, the longitude and latitude center of the center point and the zoom level of the center point are also known because they are set by ourselves, so we can calculate the tiles corresponding to the center coordinates:

// Center point corresponding tile
letcenterTile = getTileRowAndCol( ... lngLat2Mercator(... this.center),/ / 4326 3857
    this.zoom// Scale the hierarchy
)
Copy the code

The scale level is set to 17, and the central point is still the longitude and latitude of Leifeng Tower. Then the corresponding tile row and row number has been calculated before and is [109280, 53979].

We know the row and row number of the tile corresponding to the center coordinate, so we know the pixel position of the upper left corner of the tile in the world plan:

// The pixel coordinates corresponding to the upper left corner of the center tile
let centerTilePos = [centerTile[0] * TILE_SIZE, centerTile[1] * TILE_SIZE]
Copy the code

Calculated as [27975680, 13818624]. How does this coordinate translate to the screen? See the following image:

Center longitude and latitude of the tiles we calculated, tiles pixel coordinates also know that in the top left hand corner, then we could calculate the center of the latitude and longitude itself corresponding pixel coordinates, and then the top left corner of the tile difference can be calculated, finally we put the origin moves to the center of the canvas, canvas default origin for the upper left corner, the x axis is the direction to the right, y axis is the direction down). That is, the central latitude and longitude as the origin of the coordinates, then the central tile display position is this difference.

Add the method of converting latitude and longitude into pixels:

// Calculate the pixel coordinates corresponding to latitude and longitude of 4326
const getPxFromLngLat = (lng, lat, z) = > {
  let [_x, _y] = lngLat2Mercator(lng, lat)/ / 4326 3857
  // Convert to the coordinates of the world plan
  _x += EARTH_PERIMETER / 2
  _y = EARTH_PERIMETER / 2 - _y
  let resolution = resolutions[z]// The resolution of this level
  // meter/resolution to get pixels
  let x = Math.floor(_x / resolution)
  let y = Math.floor(_y / resolution)
  return [x, y]
}
Copy the code

Calculate the pixel coordinates corresponding to the longitude and latitude of the center:

// The pixel coordinates corresponding to the center point
letcenterPos = getPxFromLngLat(... this.center,this.zoom)
Copy the code

Calculate the difference:

// The difference between the center pixel coordinates and the upper left corner of the center tile
let offset = [
    centerPos[0] - centerTilePos[0],
    centerPos[1] - centerTilePos[1]]Copy the code

Finally, render the center tile through canvas:

// Move the canvas origin to the center of the canvas
this.ctx.translate(this.width / 2.this.height / 2)
// Load tile image
let img = new Image()
// Splice tile addressimg.src = getTileUrl(... centerTile,this.zoom)
img.onload = () = > {
    // Render to canvas
    this.ctx.drawImage(img, -offset[0], -offset[1])}Copy the code

Here’s a look at the implementation of getTileUrl:

// Splice tile address
const getTileUrl = (x, y, z) = > {
  let domainIndexList = [1.2.3.4]
  let domainIndex =
    domainIndexList[Math.floor(Math.random() * domainIndexList.length)]
  return `https://webrd0${domainIndex}.is.autonavi.com/appmaptile?x=${x}&y=${y}&z=${z}&lang=zh_cn&size=1&scale=1&style=8`
}
Copy the code

There are four random subfields: Webrd01, WEBRD02, WEBRD03 and WEBRD04, this is because the browser has a limit on the number of resources requested by the same domain name at the same time, and the number of tiles that need to be loaded will be larger after the local map level increases, so evenly distributed requests to each sub-domain can render all tiles more quickly and reduce the queuing time. Basically all map vendors’ tile service addresses support multiple subdomains.

To make it easier to see where the center is, we will render two additional center guides as follows:

You can see that the center is indeed Leifeng Tower, of course this is just rendering the center tile, we want tiles to cover the entire canvas, for other tiles we can calculate from the center tile, such as the one on the left of the center tile, its calculation is as follows:

// The row number of the tile is reduced by 1
let leftTile = [centerTile[0] - 1, centerTile[1]]
// Tile display coordinates, x minus a tile size, y unchanged
let leftTilePos = [
    offset[0] - TILE_SIZE * 1,
    offset[1]]Copy the code

So we just need to calculate how many tiles are needed in each of the four directions of the center tile, and then use a double loop to calculate all the tiles needed for the canvas. It is easy to calculate the number of tiles needed, as shown below:

Subtract the space occupied by the central tile from half the width and height of the canvas to get the remaining space in that direction, then divide by the size of the tile to find how many tiles are needed:

// Count tiles
let rowMinNum = Math.ceil((this.width / 2 - offset[0]) / TILE_SIZE)/ / left
let colMinNum = Math.ceil((this.height / 2 - offset[1]) / TILE_SIZE)/ /
let rowMaxNum = Math.ceil((this.width / 2 - (TILE_SIZE - offset[0])) / TILE_SIZE)/ / right
let colMaxNum = Math.ceil((this.height / 2 - (TILE_SIZE - offset[1])) / TILE_SIZE)/ /
Copy the code

Using the center tile as the origin and coordinates [0, 0], we can render all tiles with a double loop scan:

// Load tiles from top to bottom, left to right
for (let i = -rowMinNum; i <= rowMaxNum; i++) {
    for (let j = -colMinNum; j <= colMaxNum; j++) {
        // Load tile image
        let img = new Image()
        img.src = getTileUrl(
            centerTile[0] + i,/ / line number
            centerTile[1] + j,/ / column number
            this.zoom
        )
        img.onload = () = > {
            // Render to canvas
            this.ctx.drawImage(
                img, 
                i * TILE_SIZE - offset[0], 
                j * TILE_SIZE - offset[1])}}}Copy the code

The effect is as follows:

Is perfect.

Drag the

Drag can consider so, it has already been realized rendering specified latitude and longitude of the tiles, when we hold the pan, can know the mouse sliding distance, and then put the distance, namely pixels converted to latitude and longitude values and finally we will update the current center of latitude and longitude, and empty canvas, before calling method to render, Constantly repainting to create the illusion of movement.

Monitor mouse-related events:

<canvas ref="canvas" @mousedown="onMousedown"></canvas>
Copy the code
export default {
    data(){
        return {
            isMousedown: false}},mounted() {
        window.addEventListener("mousemove".this.onMousemove);
        window.addEventListener("mouseup".this.onMouseup);
    },
    methods: {
        // Mouse down
        onMousedown(e) {
            if (e.which === 1) {
                this.isMousedown = true; }},// Mouse movement
        onMousemove(e) {
            if (!this.isMousedown) {
                return;
            }
            // ...
        },

        // Release the mouse
        onMouseup() {
            this.isMousedown = false; }}}Copy the code

In onMousemove, calculate the latitude and longitude of the drag center and re-render the canvas:

// Calculate the longitude and latitude data corresponding to the drag distance
let mx = e.movementX * resolutions[this.zoom];
let my = e.movementY * resolutions[this.zoom];
// Change the longitude and latitude of the current center point to 3857 coordinates
let[x, y] = lngLat2Mercator(... this.center);// Update the latitude and longitude of the center point after dragging
center = mercatorToLngLat(x - mx, my + y);
Copy the code

The movementX and movementY properties retrieve movement values from the current and previous mouse events. Compatibility is not very good, but it is easy to calculate this value yourself. For details, go to MDN. Multiply by the current resolution to convert pixels into meters, then convert the longitude and latitude of the current center point to 3857 meters, offset the distance of this move, and finally turn back to 4326 longitude and latitude coordinates as the updated center point.

Is why is x, y, is very simple, we move the mouse to the right and down when the distance is positive, the corresponding map or move down to the right, 4326 coordinate system to the right and upward as the positive direction, then the map to the right, the center is obviously is moved to the left, because is positive to the right direction, so the center longitude direction is reduced, So it’s subtracting the distance moved, and as the map moves down, the center is moving up relatively, because it’s in the positive direction, so the latitude of the center is increasing, so plus the distance moved.

After updating the center latitude and longitude, then empty the canvas and redraw:

// Empty the canvas
this.clear();
// Redraw the renderTiles method that encapsulates the code logic from the previous section
this.renderTiles();
Copy the code

The effect is as follows:

Can see is messy, this is why, in fact, because images load is an asynchronous process, we move the mouse in the process, continue to calculate to load the tiles to load, but may be on a batch of tiles is not loaded, the mouse has been moved to a new location, a new batch of tiles and calculated to load, May be loaded on a batch of tiles and render out, but some of these tiles might have been removed canvas, don’t need to display, some may also within the canvas, but before use or location, it is wrong of rendering, at the same time a new batch of tiles may be loaded and renders, naturally led to the final show of disorder.

It is easy to know why. First, we add a cache object, because during the dragging process, many tiles only change their position. There is no need to reload, the same tile is loaded once, then only update its position. Set another object to record the tiles that should be displayed on the current canvas and prevent the tiles that should not be displayed from being rendered:

{
    // Cache tiles
    tileCache: {},
    // Record the tiles needed on the current canvas
    currentTileCache: {}}Copy the code

Because we need to record tile position, loading status, etc., we create a tile class:

/ / tiles
class Tile {
  constructor(opt = {}) {
    // Canvas context
    this.ctx = ctx
    // Tile row number
    this.row = row
    this.col = col
    // Tile level
    this.zoom = zoom
    // Display the location
    this.x = x
    this.y = y
    // a function that determines whether a tile should be rendered
    this.shouldRender = shouldRender
    Url / / tiles
    this.url = ' '
    / / the cache key
    this.cacheKey = this.row + '_' + this.col + '_' + this.zoom
    / / picture
    this.img = null
    // The image is loaded
    this.loaded = false

    this.createUrl()
    this.load()
  }
    
  / / generated url
  createUrl() {
    this.url = getTileUrl(this.row, this.col, this.zoom)
  }

  // Load the image
  load() {
    this.img = new Image()
    this.img.src = this.url
    this.img.onload = () = > {
      this.loaded = true
      this.render()
    }
  }

  // Render the image to canvas
  render() {
    if (!this.loaded || !this.shouldRender(this.cacheKey)) {
      return
    }
    this.ctx.drawImage(this.img, this.x, this.y)
  }
    
  // Update the location
  updatePos(x, y) {
    this.x = x
    this.y = y
    return this}}Copy the code

Then modify the logic before the double loop render tile:

this.currentTileCache = {}// Clear the cache object
for (let i = -rowMinNum; i <= rowMaxNum; i++) {
    for (let j = -colMinNum; j <= colMaxNum; j++) {
        // The row number of the current tile
        let row = centerTile[0] + i
        let col = centerTile[1] + j
        // Display position of the current tile
        let x = i * TILE_SIZE - offset[0]
        let y = j * TILE_SIZE - offset[1]
        / / the cache key
        let cacheKey = row + '_' + col + '_' + this.zoom
        // Record the tiles that the canvas currently needs
        this.currentTileCache[cacheKey] = true
        // The tile has been loaded
        if (this.tileCache[cacheKey]) {
            // Update to current location
            this.tileCache[cacheKey].updatePos(x, y).render()
        } else {
            // Not loaded
            this.tileCache[cacheKey] = new Tile({
                ctx: this.ctx,
                row,
                col,
                zoom: this.zoom,
                x,
                y,
                // Determine if the tile is on the current canvas cache object, if it is, it needs to be rendered
                shouldRender: (key) = > {
                    return this.currentTileCache[key]
                },
            })
        }
    }
}
Copy the code

The effect is as follows:

As you can see, the drag is working. Of course, the above implementation is still very rough, and there are a lot of areas that need to be optimized, such as:

1. Generally, the central tiles are loaded in a sequence first

2. Increasing the number of cached tiles will definitely affect performance as well, so some cleanup strategy is also needed

Those who are interested in these questions can think for themselves.

The zoom

Drag to update the latitude and longitude of the center point in real time, so zooming naturally updates the zooming level:

export default {
    data() {
        return {
            // Scale the level range
            minZoom: 3.maxZoom: 18.// Timer
            zoomTimer: null}},mounted() {
        window.addEventListener('wheel'.this.onMousewheel)
    },
    methods: {
        // Mouse scroll
        onMousewheel(e) {
            if (e.deltaY > 0) {
                // The hierarchy becomes smaller
                if (this.zoom > this.minZoom) this.zoom--
            } else {
                // The hierarchy becomes larger
                if (this.zoom < this.maxZoom) this.zoom++
            }
            // Add an anti-shake function to prevent fast rolling loading of intermediate tiles
            this.zoomTimer = setTimeout(() = > {
                this.clear()
                this.renderTiles()
            }, 300)}}}Copy the code

The effect is as follows:

The function is there, but the effect is very general, because we usually use a map zoom is a zooming in or out transition animation, and this is directly blank and re-render, not carefully do not know whether to zoom in or out.

So let’s add a transition effect where we zoom in and out of the canvas as we scroll, and then render the tiles based on the final scaling value after the animation is over.

Canvas default zoom value is 1, is on the basis of excellent with 2 x magnification is divided by two, then the animation to the target, the animation set during the canvas zoom value and empty canvas, redraw the tiles of the canvas, to zoom in and out of the visual effects, animation renderTiles again after the call to render the final scale value need tiles.

// Animations use the PopMotion library, https://popmotion.io/
import { animate } from 'popmotion'

export default {
    data() {
        return {
            lastZoom: 0.scale: 1.scaleTmp: 1.playback: null,}},methods: {
        // Mouse scroll
        onMousewheel(e) {
            if (e.deltaY > 0) {
                // The hierarchy becomes smaller
                if (this.zoom > this.minZoom) this.zoom--
            } else {
                // The hierarchy becomes larger
                if (this.zoom < this.maxZoom) this.zoom++
            }
            // The level has not changed
            if (this.lastZoom === this.zoom) {
                return
            }
            this.lastZoom = this.zoom
            // Update the scale, which is the target scale value
            this.scale *= e.deltaY > 0 ? 0.5 : 2
            // Stop the last animation
            if (this.playback) {
                this.playback.stop()
            }
            // Start animation
            this.playback = animate({
                from: this.scaleTmp,// Current scale value
                to: this.scale,// Target zoom value
                onUpdate: (latest) = > {
                    // Update the current zoom value in real time
                    this.scaleTmp = latest
                    // Save the previous state of the canvas for two reasons:
                    Scale (2,2); scale(3,3); scale(3,3); scale(3,3); scale(3,3)
                    // 2. Ensure that the scaling effect only applies to rerendering existing tiles and does not affect the final renderTiles()
                    this.ctx.save()
                    this.clear()
                    this.ctx.scale(latest, latest)
                    // Refresh tiles on the current canvas
                    Object.keys(this.currentTileCache).forEach((tile) = > {
                        this.tileCache[tile].render()
                    })
                    // Return to the previous canvas state
                    this.ctx.restore()
                },
                onComplete: () = > {
                    // Reset the zoom value to 1 after the animation is complete
                    this.scale = 1
                    this.scaleTmp = 1
                    // Recalculate the required tiles and render according to the final scaling value
                    this.renderTiles()
                },
            })
        }
    }
}
Copy the code

The effect is as follows:

It’s still pretty average, but at least you can see if you’re zooming in or out.

Coordinate system transformation

Front also left a small problem, namely, we choose gold tools in 4326 latitude and longitude, latitude and longitude directly as the front also said that there is deviation between them, such as access to mobile phone GPS latitude and longitude are usually 84 coordinates, directly in the gold map shows, will find that you is not the same as the actual location, and so is the need for a transition, There are tools available to help you do this, such as Gcoord, CoordTransform, etc.

conclusion

At present, canvas is generally used to render 2D maps. If it is not convenient to implement animations by ourselves, there are also some powerful canvas libraries to choose from. Finally, the author used konva. js library to make a new version. Add tile fade animation and the final effect looks like this:

In addition, once you know the tile rules of each map, you can modify it slightly to support more tiles:

Specific implementation is limited to space is no longer expanded, interested can read the source code.

This paper introduces a simple web map development process in detail, the above implementation principle is only the author’s personal ideas, does not represent the principle of openLayers and other frameworks, because the author is also a GIS beginner, so there will inevitably be problems, or better implementation, welcome to point out.

Online Demo: Wanglin2.github. IO /web_map_dem…

Full source: github.com/wanglin2/we…