src/Gisplay/Helpers/WebglUtils.js
/**
* Class with static methods that will help with WebGL related stuff(Matrices, web mercator projection and shaders).
* Always remeber WebGL is column major when reading the matrix code.
* @see http://ptgmedia.pearsoncmg.com/images/chap3_9780321902924/elementLinks/03fig27.jpg
* @static
* @class WebGLUtils
*/
export class WebGLUtils {
/**
* Calculates the scale and offset(X and Y) for the Web Mercator projection.
* @static
* @param {number} longitudeCenter - Longitude of the given position.
* @param {number} latitudeCenter - Latitude of the given position.
* @param {number} zoom - Current zoom level of the background map.
* @param {number} tileSize - The size of each tile in the background map. Usually is 256. If different should be given in the API options.
* @param {number} width - Width of the current canvas.
* @param {number} height - Height of the current canvas.
* @returns {{scale: number, offsetX: number, offsetY: number}} - Returns the scale, offsetX and offsetY of the point given using the Web Mercator projection.
* @see https://bl.ocks.org/enjalot/fb7f3d696167e9b83a72#viewport.js
* @see https://en.wikipedia.org/wiki/Web_Mercator
* @memberOf WebGLUtils
*/
static webMercatorProjection(longitudeCenter, latitudeCenter, zoom, tileSize, width, height) {
// console.log(longitudeCenter, latitudeCenter, zoom, tileSize, width, height);
let PI = Math.PI;
let scale = ((tileSize / 2) / PI) * Math.pow(2, zoom);
let lambda = longitudeCenter * (PI / 180); // Convert longitude to radians
let phi = latitudeCenter * (PI / 180); // Convert latitude to radians
let xCenter = scale * (lambda + PI);
let yCenter = scale * (PI - Math.log(Math.tan((PI / 4) + (phi / 2))));
let offsetX = (width / 2) - xCenter;
let offsetY = (height / 2) - yCenter;
return { scale: scale, offsetX: offsetX, offsetY: offsetY };
}
/**
* This is the result matrix from the multiplication of M1*M2*M3
* @static
* @param {number} scale - The scale calculated with WebMercator projection.
* @param {number} width - The width of the canvas.
* @param {number} height - The height of the canvas.
* @param {number} offsetX - The offsetX calculated with WebMercator projection.
* @param {number} offsetY - The offsetY calculated with WebMercator projection.
* @returns {Float32Array} The resulting matrix (M1*M2*M3) in a single matrix to send to WebGL in order to calculate the resulting position.
* @see Rui's thesis
* @memberOf WebGLUtils
*/
static finalMatrix(scale, width, height, offsetX, offsetY) {
let p0 = (2 * Math.PI * scale) / (width * 180);
let p2 = ((2 * Math.PI * scale) / width) + ((2 * offsetX) / width) - 1;
let p4 = (2 * scale) / height;
let p5 = ((2 * offsetY / height) - 1);
return new Float32Array([
p0, 0, 0,
0, p4, 0,
p2, p5, 1
]);
}
/**
* Creates shaders(Vertex + Fragment) source code.
* @static
* @returns {{vertexCode: string, fragmentCode: string}} - The code for the vertex and fragment shaders.
* @memberOf WebGLUtils
*/
static generateShadersSourceCode() {
let vertexSourceCode =
`
#define PI radians(180.0)
attribute vec2 coords;
uniform mat3 M;
attribute float aPointSize;
attribute float a_opacity;
varying float v_opacity;
void main() {
float phi = coords[1] * (PI / 180.0);
float YValue = PI -log( tan((PI/4.0) + phi/2.0) );
vec3 f = vec3(coords[0], YValue, 1.0);
vec3 pixeis = M * f;
float X = pixeis[0];
float Y = -(pixeis[1]);
gl_Position = vec4(X, Y , 0.0, 1.0);
gl_PointSize = aPointSize;
v_opacity = a_opacity;
}
`;
let fragmentSourceCode =
`
precision mediump float;
uniform vec4 u_color;
varying float v_opacity;
uniform float isPoint;
void main() {
float border = 0.5;
float radius = 0.5;
float centerDist = length(gl_PointCoord - 0.5);
float alpha;
if (u_color[3] == -1.0)
alpha = v_opacity * step(centerDist, radius);
else
alpha = u_color[3] * step(centerDist, radius);
if(isPoint == 1.0 ) {
if (alpha < 0.1) discard;
gl_FragColor = vec4(u_color[0], u_color[1], u_color[2], alpha);
}
else
gl_FragColor = vec4(u_color[0], u_color[1], u_color[2], u_color[3]);
}
`;
return { vertexCode: vertexSourceCode, fragmentCode: fragmentSourceCode };
}
/**
* Creates and compiles a shader.
* @static
* @param {string} type - Type of shader. Options are: VERTEX_SHADER or FRAGMENT_SHADER;
* @param {string} source_code - The shader source code.
* @param {Map#_webgl} webgl - Webgl object used by the Map class.
* @returns {WebGLShader} - The shader(vertex of fragment).
* @memberOf WebGLUtils
*/
static createAndCompileShader(type, source_code, webgl) {
let shader = webgl.gl.createShader(type);
webgl.gl.shaderSource(shader, source_code);
webgl.gl.compileShader(shader);
return shader;
}
/**
* Initializes:
* 1)WebGLProgram, 2) Generates shadders, 3) Attaches shaders to the program, 4) links program, 5) uses program.
* @static
* @param {{gl: WebGLRenderingContext, program: WebGLProgram}} webgl
* @returns {void}
* @memberOf WebGLUtils
*/
static createWebGLProgram(webgl) {
webgl.program = webgl.gl.createProgram();
const source_code = this.generateShadersSourceCode();
const vertex_shader = this.createAndCompileShader(webgl.gl.VERTEX_SHADER, source_code.vertexCode, webgl);
const fragment_shader = this.createAndCompileShader(webgl.gl.FRAGMENT_SHADER, source_code.fragmentCode, webgl);
webgl.gl.attachShader(webgl.program, vertex_shader);
webgl.gl.attachShader(webgl.program, fragment_shader);
webgl.gl.linkProgram(webgl.program);
webgl.gl.useProgram(webgl.program);
}
}