A brief analysis of Mapbox GL JS

A brief analysis of Mapbox GL JS

Introduction to MapboxGLJS

Mapbox was founded in 2010 with the goal of providing an alternative to Google Map. At that time, Google Map almost monopolized all online map business, but in Google Map, there was almost no possibility of customization, and there was no tool to allow cartographers to create maps according to their ideas. Mapbox was founded to change this situation and provide cartographers and developers with tools to create the maps they want. It is worth mentioning that almost all of the mapping tools provided by Mapbox are open source. Mapbox currently mainly provides geographic data, rendering clients and other map-related services. Mapbox GL JS is one of their open source client libraries for rendering interactive maps on the Web. As part of the Mapbox ecosystem, it is usually integrated with other services provided by Mapbox for unified external use. At present, Mapbox's main business, in addition to map-related products, also includes LBS (Location Based Services) services, autonomous driving, own data (Boundaries, Traffic Data, Movement), and vehicle services. Mapbox GL JS is a JavaScript library that uses WebGL technology to organize data in vector tiles , configure map style rules in Mapbox styles , and finally render an interactive map. Another part of the Mapbox GL ecosystem is Mapbox Mobile , which is a rendering engine written in C++ that is compatible with desktop and mobile platforms.

Advantages of Mapbox GL JS as a map engine

Compared to Leaflet, OpenStreetMap and other 2D raster map map engines

  • Mapbox GL JS uses vector data to render the map (raster data can be used). Vector data is easy to change the feature style and layer order, and can also be updated quickly when the data version is iterated.

  • Compared with raster images, vector data has more data compression possibilities; therefore, in the same scene, the data volume of vector data is smaller than that of raster data (vector data needs to be organized properly).

  • Mapbox GL JS uses the WebGL solution, which can support more 3D map effects than Canvas and SVG solutions.

Compared to Cesium and other 3D map engines

  • The rendering scene of Mapbox GL JS is actually 2.5D, which is not a complete 3D scene (controlling the viewing angle), which makes the visual range of the map controllable, and the data can be organized using a two-dimensional pyramid model, which improves the performance of data filtering in the scene , Also controls the amount of data displayed on the same screen.
  • Mapbox GL JS has a relatively small volume and a clear module structure, making it easy to carry out transformation and secondary development.

Compared to Google Map, AB Map (Mapbox GL JS versions 1.x)

  • Mapbox GL JS is open source software with a rich community ecology, and it is also easy to transform and re-development.
  • The open source agreement of Mapbox GL JS is BSD-3-Clause license, which can be used legally for commercialization.
  • The map solution provided by the Mapbox GL system supports the deployment of map data services in the intranet and can isolate the intranet and the external network.
  • 2.x version has changed the agreement
    Reference News

Mapbox GL JS v2 has a completely different, proprietary license. It is not free and not truly open anymore.

How to create a map

1. Preparation

  • Applying for access_token access_token is an authentication method of Mapbox. API requests can be associated with user accounts through access tokens. In development, calling map data and using the corresponding API or SDK require the developer's access_token to have corresponding permissions.

  • Prepare Mapbox Style url or Mapbox Style object Mapbox Style is a document that defines the visual appearance of the map: what data is drawn, in what order, and how to set the data style when drawing the data; Mapbox Style objects are JSON with specific root levels and nested properties Objects, including information about data sources, layer styles, Sprite, text fonts, metadata, etc.;

Style usually comes from three sources:

  1. Use the standard style provided by the Mapbox official website

  1. Use Mapbox Studio provided by Mapbox to configure custom map styles

  1. Writing style JSON object manually copy the code
{ "version" : 8 , "name" : "Void" , "metadata" : {}, "sources" : {}, "sprite" : "mapbox://sprites/mapbox/basic-v9" , "glyphs" : "mapbox://fonts/{fontstack}/{range}.pbf" , "layers" : [] } Copy code

2. Create a map

  • Use Mapbox CDN

Include the js and css files provided by Mapbox GL JS in the HTML file:

<script src = 'https://api.mapbox.com/mapbox-gl-js/v2.2.0/mapbox-gl.js' ></script> < link href = 'https://api.mapbox.com/mapbox-gl-js/v2.2.0/mapbox-gl.css' the rel = 'this stylesheet'/> copy the code

Add the map container DOM and the corresponding script code to the HTML file:

<div id = 'map' style = 'width: 400px; height: 300px;' ></div> < script > mapboxgl.accessToken = '<your access token here>' ; var map = new mapboxgl.Map({ container : 'map' , //container DOM id style : 'mapbox://styles/mapbox/streets-v11' , //style URL center : [- 74.5 , 40 ], //the initial center point of the map [lng, lat] zoom : 9 //the initial zoom of the map }); </script > copy code
  • Use package introduction

Install npm package

npm install --save mapbox-gl duplicated code

The js and css files provided by Mapbox GL JS can be introduced into the HTML file

<link href='https://api.mapbox.com/mapbox-gl-js/v2.2.0/mapbox-gl.css' rel='stylesheet'/> Copy code

You can also use CSS loader to directly import in the js file

import'mapbox-gl/dist/mapbox-gl.css'; copy the code

Create a map in the project

Import mapboxgl from 'mapboxgl' ; //or "const = mapboxgl the require ( 'mapboxgl');" mapboxgl.accessToken = '<your access token here>' ; const map = new mapboxgl.Map({ container : 'map' , //container DOM id style : 'mapbox://styles/mapbox/streets-v11' , //style URL center : [- 74.5 , 40 ], //Initial map center point [lng, lat] zoom : 9 //Initial map zoom }); Copy code

Mapbox GL JS architecture

Data organization

Mapbox GL JS uses Web Mercator projection, which makes the world a square in the map; all the data is equally distributed in a square grid of the same size with different resolutions according to the scale.

The Mercator projection is a positive-axis equiangular cylindrical projection. It was founded in 1569 by the Dutch cartographyist G.Mercator. Imagine that a cylinder consistent with the direction of the earth s axis is cut or cut on the earth. According to the equiangular condition, the graticule is projected onto the cylindrical surface, and the cylindrical surface is expanded into a plane to obtain this projection. In the Mercator projection, the tangent cylindrical projection and the secant cylindrical projection, the earliest and most commonly used is the tangent cylindrical projection. !

(tileset) Mapbox tileset SDK tileset

  • Interface Map Camera Marker Popup Control Event Handler move, click, zoom......

  • Style Mapbox Style Layer -Line -Fill Mapbox Style layer Source Mapbox Style source Style Light

  • Render WebGL Paint Painter Fill, Line, Symbol...... Draw Function Shader

  • Map Data Source Source Source Worker Source

  • Tile Data WebGL Source Source Layer Bucket

  • Util

  • Source, Tile, Bucket, Layer Source Tile Source Layer Layer Tile Bucket

1. Map

Map Style Mapbox Style

//src/ui/map.js this.setStyle(options.style, {localFontFamily: this._localFontFamily, localIdeographFontFamily: this._localIdeographFontFamily});

Style source layer

//src/style/style.js for (const id in json.sources) { this.addSource(id, json.sources[id], {validate: false}); }
//src/style/style.js for (let layer of layers) { layer = createStyleLayer(layer); layer.setEventedParent(this, {layer: {id: layer.id}}); this._layers[layer.id] = layer; this._serializedLayers[layer.id] = layer.serialize(); this._updateLayerCount(layer, true); }

source Source SourceCache layer Layer layer (layout, paint) Map




Map _update

//src/ui/map.js this.style._updateSources(this.transform);

Style SourceCache

//src/style/style.js this._sourceCaches[id].update(transform);

SourceCache Source /

//src/source/source_cache.js const tile = this._addTile(tileID); //.. const parentId = tileID.scaledTo(overscaledZ);

Source worker

//src/source/vector_tile_source.js tile.request = tile.actor.send('loadTile', params, done.bind(this), undefined, true);

worker Bucket Source Source SourceCache

//src/source/vector_tile_worker_source.js tile.loadVectorData(data, this.map.painter);


//src/ui/map.js //Actually draw this.painter.render(this.style, { showTileBoundaries: this.showTileBoundaries, showTerrainWireframe: this.showTerrainWireframe, showOverdrawInspector: this._showOverdrawInspector, showQueryGeometry: !!this._showQueryGeometry, rotating: this.isRotating(), zooming: this.isZooming(), moving: this.isMoving(), fadeDuration, isInitialLoad: this._isInitialLoad, showPadding: this.showPadding, gpuTiming: !!this.listens('gpu-timing-layer'), speedIndexTiming: this.speedIndexTiming, });

Painter SourceCache

//src/render/painter.js for (const id in sourceCaches) { const sourceCache = sourceCaches[id]; if (sourceCache.used) { sourceCache.prepare(this.context); } }

SourceCache Tile

//src/source/source_cache.js for (const i in this._tiles) { const tile = this._tiles[i]; tile.upload(context); tile.prepare(this.map.style.imageManager); }

Tile Bucket

//src/source/tile.js for (const id in this.buckets) { const bucket = this.buckets[id]; if (bucket.uploadPending()) { bucket.upload(context); } }

Bucket WebGL Buffer

//src/data/bucket/fill_bucket.js if (!this.uploaded) { this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray, layoutAttributes); this.indexBuffer = context.createIndexBuffer(this.indexArray); this.indexBuffer2 = context.createIndexBuffer(this.indexArray2); }

WebGL Layer Frameuffer

//Offscreen pass =============================================== //We first do all rendering that requires rendering to a separate //framebuffer, and then save those for rendering back to the map //later: in doing this we avoid doing expensive framebuffer restores. this.renderPass = 'offscreen';


//src/render/painter.js //Rebind the main framebuffer now that all offscreen layers have been rendered: this.context.bindFramebuffer.set(null); this.context.viewport.set([0, 0, this.width, this.height]); //Clear buffers in preparation for drawing to the main framebuffer this.context.clear({color: options.showOverdrawInspector ? Color.black : Color.transparent, depth: 1}); this.clearStencil();

//src/render/painter.js //Opaque pass =============================================== //Draw opaque layers top-to-bottom first. this.renderPass = 'opaque'; //...
//src/render/painter.js //Translucent pass =============================================== //Draw all other layers bottom-to-top. this.renderPass = 'translucent'; //...

Layer Layer Tile Tile Tile

//src/render/painter.js this._renderTileClippingMasks(layer, sourceCache, coords);

//src/render/painter.js this.renderLayer(this, sourceCache, layer, coords);

Layer Draw Function Fill Tile

//src/render/draw_fill.js drawFillTiles(painter, sourceCache, layer, coords, depthMode, colorMode, false);

WebGL Program

//src/render/draw_fill.js const program = painter.useProgram(programName, programConfiguration);


//src/render/draw_fill.js fillUniformValues(tileMatrix);

//src/render/draw_fill.js program.draw(painter.context, drawMode, depthMode, painter.stencilModeForClipping(coord), colorMode, CullFaceMode.disabled, uniformValues, layer.id, bucket.layoutVertexBuffer, indexBuffer, segments, layer.paint, painter.transform.zoom, programConfiguration);

Program Uniform

//src/render/program.js if (configuration) { configuration.setUniforms(context, this.binderUniforms, currentProperties, {zoom: (zoom: any)}); }


//src/render/program.js vao.bind( context, this, layoutVertexBuffer, configuration ? configuration.getPaintVertexBuffers() : [], indexBuffer, segment.vertexOffset, dynamicLayoutBuffer, dynamicLayoutBuffer2 );

//src/render/program.js gl.drawElements( drawMode, segment.primitiveLength * primitiveSize, gl.UNSIGNED_SHORT, segment.primitiveOffset * primitiveSize * 2);


web excel WebIDE

ByteFE tech@bytedance.com