Heatmap on custom layers
vanilla.html
react.html
vue.html
common.ts
heatmap-gl-impl.ts
variables.ts
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
<!-- To make the map appear, you must add your apikey -->
<script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>
<script
data-plugins="transform-modules-umd"
data-presets="typescript"
type="text/babel"
src="../variables.ts"
></script>
<script
data-plugins="transform-modules-umd"
data-presets="typescript"
type="text/babel"
src="./common.ts"
></script>
<script
data-plugins="transform-modules-umd"
data-presets="typescript"
type="text/babel"
src="../heatmap-gl-impl.ts"
></script>
<script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
import {getRandomPoints, getDefaultMapProps} from './common';
import {getHeatmapImpl} from '../heatmap-gl-impl';
window.map = null;
main();
async function main() {
// Waiting for all api elements to be loaded
await mappable.ready;
const {MMap, MMapDefaultSchemeLayer, MMapLayer} = mappable;
const {location: defaultLocation, projection: defaultProjection} = getDefaultMapProps();
// Initialize the map
map = new MMap(
// Pass the link to the HTMLElement of the container
document.getElementById('app'),
// Pass the map initialization parameters
{location: defaultLocation, showScaleInCopyrights: true, projection: defaultProjection},
// Add a map scheme layer
[new MMapDefaultSchemeLayer({})]
);
const points = getRandomPoints(1500);
// Add a custom layer to the map
map.addChild(
new MMapLayer({
id: 'heatmap',
type: 'custom',
zIndex: 1201,
grouppedWith: `${MMapDefaultSchemeLayer.defaultProps.source}:buildings`,
source: MMapDefaultSchemeLayer.defaultProps.source,
implementation: ({effectiveMode}) =>
effectiveMode === 'vector' ? getHeatmapImpl(defaultProjection, () => points) : undefined
})
);
}
</script>
<!-- prettier-ignore -->
<style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; } </style>
</head>
<body>
<div id="app"></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<script crossorigin src="https://cdn.jsdelivr.net/npm/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/react-dom@17/umd/react-dom.production.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
<!-- To make the map appear, you must add your apikey -->
<script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>
<script
data-plugins="transform-modules-umd"
data-presets="typescript"
type="text/babel"
src="../variables.ts"
></script>
<script
data-plugins="transform-modules-umd"
data-presets="react, typescript"
type="text/babel"
src="./common.ts"
></script>
<script
data-plugins="transform-modules-umd"
data-presets="typescript"
type="text/babel"
src="../heatmap-gl-impl.ts"
></script>
<script data-plugins="transform-modules-umd" data-presets="react, typescript" type="text/babel">
import {getDefaultMapProps, getRandomPoints} from './common';
import {getHeatmapImpl} from '../heatmap-gl-impl';
window.map = null;
main();
async function main() {
// For each object in the JS API, there is a React counterpart
// To use the React version of the API, include the module @mappable-world/mappable-reactify
const [mappableReact] = await Promise.all([
mappable.import('@mappable-world/mappable-reactify'),
mappable.ready
]);
const reactify = mappableReact.reactify.bindTo(React, ReactDOM);
const {MMap, MMapDefaultSchemeLayer, MMapLayer} = reactify.module(mappable);
const {useState} = React;
const points = getRandomPoints(1500);
const {location: defaultLocation, projection: defaultProjection} = getDefaultMapProps();
function App() {
const [location, setLocation] = useState(defaultLocation);
return (
// Initialize the map and pass initialization parameters
<MMap
location={location}
projection={defaultProjection}
showScaleInCopyrights={true}
ref={(x) => (map = x)}
>
{/* Add a map scheme layer */}
<MMapDefaultSchemeLayer />
<MMapLayer
id={'heatmap'}
type={'custom'}
zIndex={1201}
grouppedWith={mappable.MMapDefaultSchemeLayer.defaultProps.source + ':buildings'}
source={mappable.MMapDefaultSchemeLayer.defaultProps.source}
implementation={React.useCallback(
({effectiveMode}) =>
effectiveMode === 'vector' ? getHeatmapImpl(defaultProjection, () => points) : undefined,
[]
)}
/>
</MMap>
);
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('app')
);
}
</script>
<!-- prettier-ignore -->
<style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; } </style>
</head>
<body>
<div id="app"></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<script crossorigin src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
<script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>
<script
data-plugins="transform-modules-umd"
data-presets="typescript"
type="text/babel"
src="../variables.ts"
></script>
<script
data-plugins="transform-modules-umd"
data-presets="typescript"
type="text/babel"
src="./common.ts"
></script>
<script
data-plugins="transform-modules-umd"
data-presets="typescript"
type="text/babel"
src="../heatmap-gl-impl.ts"
></script>
<script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
import {getDefaultMapProps, getRandomPoints} from './common';
import {getHeatmapImpl} from '../heatmap-gl-impl';
window.map = null;
async function main() {
const [mappableVue] = await Promise.all([mappable.import('@mappable-world/mappable-vuefy'), mappable.ready]);
const vuefy = mappableVue.vuefy.bindTo(Vue);
const {MMap, MMapDefaultSchemeLayer, MMapControls, MMapLayer} = vuefy.module(mappable);
const {MMapZoomControl} = vuefy.module(await mappable.import('@mappable-world/mappable-controls@0.0.1'));
const {location: defaultLocation, projection: defaultProjection} = getDefaultMapProps();
const points = getRandomPoints(1500);
const app = Vue.createApp({
components: {MMap, MMapDefaultSchemeLayer, MMapControls, MMapZoomControl, MMapLayer},
setup() {
const refMap = (ref) => {
window.map = ref?.entity;
};
return {
LOCATION: defaultLocation,
refMap,
projection: defaultProjection,
implementation: ({effectiveMode}) => {
return effectiveMode === 'vector' ? getHeatmapImpl(defaultProjection, () => points) : undefined;
}
};
},
template: `
<MMap :location="LOCATION" :showScaleInCopyrights="true" :ref="refMap" :projection="projection">
<MMapDefaultSchemeLayer />
<MMapLayer
id="heatmap"
type="custom"
:zIndex=1201
grouppedWith="${mappable.MMapDefaultSchemeLayer.defaultProps.source}:buildings"
source="${mappable.MMapDefaultSchemeLayer.defaultProps.source}"
:implementation="implementation"
/>
<MMapControls position="right">
<MMapZoomControl></MMapZoomControl>
</MMapControls>
</MMap>`
});
app.mount('#app');
}
main();
</script>
<!-- prettier-ignore -->
<style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; } </style>
</head>
<body>
<div id="app"></div>
</body>
</html>
import type {LngLat} from '@mappable-world/mappable-types';
import {LOCATION} from './variables';
export function getDefaultMapProps() {
return {
location: LOCATION,
projection: mappable.projections.sphericalMercator
};
}
const seed = (s) => () => {
s = Math.sin(s) * 10000;
return s - Math.floor(s);
};
export const rnd = seed(10000);
export type Point = {coordinates: LngLat; value: number};
function rndSign(): number {
return rnd() > 0.5 ? 1 : -1;
}
export function getRandomPoints(count: number, delta: number = 1.5): Point[] {
const result: Point[] = [];
for (let i = 0; i < count; i++) {
const coordinates: LngLat = [
LOCATION.center[0] + rnd() * delta * rndSign(),
LOCATION.center[1] + rnd() * delta * rndSign()
];
const value = rnd() * 1;
result.push({coordinates, value});
}
return result;
}
import type {
PixelCoordinates,
Projection,
WorldCoordinates,
VectorLayerImplementation,
VectorLayerImplementationConstructor,
VectorLayerImplementationRenderProps
} from '@mappable-world/mappable-types';
import type {Point} from './common';
type FrameBuffer = {
color: WebGLTexture;
depth: WebGLTexture;
buffer: WebGLFramebuffer;
size: PixelCoordinates;
};
type Program = {
attributes: Record<string, number>;
uniforms: Record<string, WebGLUniformLocation>;
program: WebGLProgram;
vs: WebGLShader;
fs: WebGLShader;
vao: WebGLVertexArrayObjectOES;
};
type RenderWorldProps = Omit<VectorLayerImplementationRenderProps, 'worlds'> & {
world: VectorLayerImplementationRenderProps['worlds'][0];
};
type DrawBuffer = {
count: number;
buffer: WebGLBuffer;
};
export const getHeatmapImpl = (
projection: Projection,
getData: () => Point[],
options: {
gradient: MappedGradient;
density: number;
max: number;
blur: number;
size: number;
} = {
gradient: gradientMapper(defaultGradient),
density: 4,
max: 1,
blur: 1,
size: 8
}
): VectorLayerImplementationConstructor => {
return class {
private _requestRender: () => void;
private _mainFrameBuffer: FrameBuffer;
private _gradientFrameBuffer: FrameBuffer;
private _gradientProgram: Program;
private _heatmapProgram: Program;
private _vertexBuffer: DrawBuffer;
private _intensityBuffer: DrawBuffer;
private _heatmapVertexBuffer: DrawBuffer;
private _heatmapTextCoordBuffer: DrawBuffer;
constructor(
private readonly gl: WebGLRenderingContext,
options: {size: PixelCoordinates; requestRender: () => void}
) {
this._requestRender = options.requestRender;
// This one is used to main rendering
this._mainFrameBuffer = this.__createFrameBuffer();
// This one is used to render the heatmap
this._gradientFrameBuffer = this.__createFrameBuffer();
this._gradientProgram = createProgram(gl, GRADIENT_SHADERS.vertex, GRADIENT_SHADERS.fragment, {
attributes: ['a_position', 'a_intensity'],
uniforms: ['u_viewProjectionMatrix', 'u_lookAt', 'u_size', 'u_density', 'u_max', 'u_blur']
});
this._heatmapProgram = createProgram(gl, HEATMAP_SHADERS.vertex, HEATMAP_SHADERS.fragment, {
attributes: ['a_position', 'a_texCoord'],
uniforms: ['u_gradient', 'u_colorArr', 'u_offset', 'u_opacity']
});
const {vertexData, intensityData} = generateVertexData(projection, getData());
// Buffers for gradient points rendering
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);
this._vertexBuffer = {count: vertexData.length / 2, buffer: vertexBuffer};
const intensityBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, intensityBuffer);
gl.bufferData(gl.ARRAY_BUFFER, intensityData, gl.STATIC_DRAW);
this._intensityBuffer = {count: intensityData.length, buffer: intensityBuffer};
// Buffers for heatmap rendering
const heatmapVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, heatmapVertexBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0]),
gl.STATIC_DRAW
);
this._heatmapVertexBuffer = {count: 6, buffer: heatmapVertexBuffer};
const heatmapTexCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, heatmapTexCoordBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0]),
gl.STATIC_DRAW
);
this._heatmapTextCoordBuffer = {count: 6, buffer: heatmapTexCoordBuffer};
}
/**
* Create frame buffer with color and depth textures
*/
private __createFrameBuffer() {
const {gl} = this;
const buffer = gl.createFramebuffer();
const colorTexture = gl.createTexture();
const depthTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, colorTexture);
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.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.getExtension('WEBGL_depth_texture');
gl.bindTexture(gl.TEXTURE_2D, depthTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindFramebuffer(gl.FRAMEBUFFER, buffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTexture, 0);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, 0);
return {
color: colorTexture,
depth: depthTexture,
buffer,
size: {x: 0, y: 0}
};
}
destroy() {
const {gl} = this;
for (const buffer of [this._mainFrameBuffer, this._gradientFrameBuffer]) {
gl.deleteFramebuffer(buffer.buffer);
gl.deleteTexture(buffer.color);
gl.deleteTexture(buffer.depth);
}
for (const program of [this._gradientProgram, this._heatmapProgram]) {
gl.deleteProgram(program.program);
gl.deleteShader(program.vs);
gl.deleteShader(program.fs);
gl.getExtension('OES_vertex_array_object').deleteVertexArrayOES(program.vao);
}
for (const buffer of [
this._vertexBuffer,
this._intensityBuffer,
this._heatmapVertexBuffer,
this._heatmapTextCoordBuffer
]) {
gl.deleteBuffer(buffer.buffer);
}
}
render({size, worlds}: Parameters<VectorLayerImplementation['render']>[0]) {
const {gl} = this;
for (const {lookAt, viewProjMatrix} of worlds) {
this.__renderGradient(size, lookAt, viewProjMatrix);
this.__renderHeatmap(size);
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.useProgram(null);
gl.getExtension('OES_vertex_array_object').bindVertexArrayOES(null);
return {
color: this._mainFrameBuffer.color,
depth: this._mainFrameBuffer.depth
};
}
/**
* Bind frame buffer and set viewport size
*/
private __bindFrameBuffer(size: PixelCoordinates, fb: FrameBuffer) {
const {gl} = this;
gl.bindFramebuffer(gl.FRAMEBUFFER, fb.buffer);
gl.viewport(0, 0, size.x, size.y);
if (fb.size.x === size.x && fb.size.y === size.y) {
return;
}
fb.size = size;
gl.bindTexture(gl.TEXTURE_2D, fb.color);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size.x, size.y, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.bindTexture(gl.TEXTURE_2D, fb.depth);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.DEPTH_COMPONENT,
size.x,
size.y,
0,
gl.DEPTH_COMPONENT,
gl.UNSIGNED_INT,
null
);
}
/**
* Drawing points in the form of circles with blur
*/
private __renderGradient(
size: PixelCoordinates,
lookAt: WorldCoordinates,
viewProjMatrix: RenderWorldProps['world']['viewProjMatrix']
) {
const {gl} = this;
this.__bindFrameBuffer(size, this._gradientFrameBuffer);
gl.disable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
const {vao, program, attributes, uniforms} = this._gradientProgram;
gl.useProgram(program);
gl.getExtension('OES_vertex_array_object').bindVertexArrayOES(vao);
gl.uniformMatrix4fv(uniforms.u_viewProjectionMatrix, false, viewProjMatrix as Float32List);
gl.uniform2f(uniforms.u_lookAt, -lookAt.x, -lookAt.y);
gl.uniform1f(uniforms.u_density, options.density);
gl.uniform1f(uniforms.u_max, options.max);
gl.uniform1f(uniforms.u_size, options.size);
gl.uniform1f(uniforms.u_blur, options.blur);
// We can optimize this and set only once but for example purposes we set it every time
// https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#avoid_changing_vao_attachments_vertexattribpointer_disableenablevertexattribarray
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer.buffer);
gl.enableVertexAttribArray(attributes.a_position);
gl.vertexAttribPointer(attributes.a_position, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._intensityBuffer.buffer);
gl.enableVertexAttribArray(attributes.a_intensity);
gl.vertexAttribPointer(attributes.a_intensity, 1, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.POINTS, 0, this._vertexBuffer.count);
}
/**
* Draw a rectangle on the entire screen and apply a texture from the framebuffer to it
*/
private __renderHeatmap(size: PixelCoordinates) {
const {gl} = this;
const {vao, program, attributes, uniforms} = this._heatmapProgram;
this.__bindFrameBuffer(size, this._mainFrameBuffer);
gl.enable(gl.DEPTH_TEST);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(program);
gl.getExtension('OES_vertex_array_object').bindVertexArrayOES(vao);
gl.uniform4fv(uniforms.u_colorArr, options.gradient.value);
gl.uniform1fv(uniforms.u_offset, new Float32Array(options.gradient.offset));
gl.uniform1f(uniforms.u_opacity, 1);
gl.activeTexture(gl.TEXTURE0 + 0);
gl.bindTexture(gl.TEXTURE_2D, this._gradientFrameBuffer.color);
gl.uniform1i(uniforms.u_gradient, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._heatmapVertexBuffer.buffer);
gl.enableVertexAttribArray(attributes.a_position);
gl.vertexAttribPointer(attributes.a_position, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._heatmapTextCoordBuffer.buffer);
gl.enableVertexAttribArray(attributes.a_texCoord);
gl.vertexAttribPointer(attributes.a_texCoord, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
};
};
/**
* Create a program with vertex and fragment shaders
*/
function createProgram(
gl: WebGLRenderingContext,
vertexShader: string,
fragmentShader: string,
options: {
attributes?: string[];
uniforms?: string[];
} = {}
): Program {
const program = gl.createProgram()!;
const vs = gl.createShader(gl.VERTEX_SHADER)!;
const fs = gl.createShader(gl.FRAGMENT_SHADER)!;
gl.shaderSource(vs, vertexShader);
gl.compileShader(vs);
gl.attachShader(program, vs);
gl.shaderSource(fs, fragmentShader);
gl.compileShader(fs);
gl.attachShader(program, fs);
gl.linkProgram(program);
const attributes = {};
for (const attribute of options.attributes ?? []) {
attributes[attribute] = gl.getAttribLocation(program, attribute);
}
const uniforms = {};
for (const uniform of options.uniforms ?? []) {
uniforms[uniform] = gl.getUniformLocation(program, uniform);
}
return {
program,
vs,
fs,
attributes,
uniforms,
vao: gl.getExtension('OES_vertex_array_object').createVertexArrayOES()
};
}
// The shader draws dots on the screen and, depending on the intensity, blurs from the center to the edge
const GRADIENT_SHADERS = {
vertex: `
attribute vec2 a_position;
attribute float a_intensity;
uniform mat4 u_viewProjectionMatrix;
uniform vec2 u_lookAt;
uniform float u_size;
uniform float u_density;
varying float v_intensity;
void main() {
vec4 offsetPosition = vec4(a_position + u_lookAt, 0.000003, 1);
vec4 position = u_viewProjectionMatrix * offsetPosition;
gl_Position = position;
gl_PointSize = u_size * u_density;
v_intensity = a_intensity;
}
`,
fragment: `
precision mediump float;
uniform float u_max;
uniform float u_blur;
varying float v_intensity;
void main() {
float r = 0.0;
vec2 cxy = 2.0 * gl_PointCoord - 1.0;
r = dot(cxy, cxy);
if(r <= 1.0) {
gl_FragColor = vec4(0, 0, 0, (v_intensity/u_max) * u_blur * (1.0 - sqrt(r)));
}
}
`
};
/**
* A shader that simply draws a rectangle on the entire screen and overlays a texture from the frame buffer on it
*/
const HEATMAP_SHADERS = {
vertex: `
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0.000001, 1);
v_texCoord = a_texCoord;
}
`,
fragment: `
precision mediump float;
uniform sampler2D u_gradient;
uniform vec4 u_colorArr[11];
uniform float u_offset[11];
uniform float u_opacity;
varying vec2 v_texCoord;
float remap ( float minval, float maxval, float curval ) {
return ( curval - minval ) / ( maxval - minval );
}
void main() {
float alpha = texture2D(u_gradient, v_texCoord.xy).a;
if (alpha > 0.0 && alpha <= 1.0) {
vec4 color;
if (alpha <= u_offset[0]) {
color = u_colorArr[0];
} else if (alpha <= u_offset[1]) {
color = mix( u_colorArr[0], u_colorArr[1], remap( u_offset[0], u_offset[1], alpha ) );
} else if (alpha <= u_offset[2]) {
color = mix( u_colorArr[1], u_colorArr[2], remap( u_offset[1], u_offset[2], alpha ) );
} else if (alpha <= u_offset[3]) {
color = mix( u_colorArr[2], u_colorArr[3], remap( u_offset[2], u_offset[3], alpha ) );
} else if (alpha <= u_offset[4]) {
color = mix( u_colorArr[3], u_colorArr[4], remap( u_offset[3], u_offset[4], alpha ) );
} else if (alpha <= u_offset[5]) {
color = mix( u_colorArr[4], u_colorArr[5], remap( u_offset[4], u_offset[5], alpha ) );
} else if (alpha <= u_offset[6]) {
color = mix( u_colorArr[5], u_colorArr[6], remap( u_offset[5], u_offset[6], alpha ) );
} else if (alpha <= u_offset[7]) {
color = mix( u_colorArr[6], u_colorArr[7], remap( u_offset[6], u_offset[7], alpha ) );
} else if (alpha <= u_offset[8]) {
color = mix( u_colorArr[7], u_colorArr[8], remap( u_offset[7], u_offset[8], alpha ) );
} else if (alpha <= u_offset[9]) {
color = mix( u_colorArr[8], u_colorArr[9], remap( u_offset[8], u_offset[9], alpha ) );
} else if (alpha <= u_offset[10]) {
color = mix( u_colorArr[9], u_colorArr[10], remap( u_offset[9], u_offset[10], alpha ) );
} else {
color = vec4(0.0, 0.0, 0.0, 0.0);
}
color.a = color.a - (1.0 - u_opacity);
if (color.a < 0.0) {
discard;
}
gl_FragColor = color;
}
}
`
};
export interface Gradient {
color: [number, number, number, number];
offset: number;
}
interface MappedGradient {
value: Float32Array;
length: number;
offset: number[];
}
const defaultGradient: Gradient[] = [
{
color: [255, 255, 255, 0.0],
offset: 0
},
{
color: [212, 225, 255, 1.0],
offset: 0.2
},
{
color: [166, 255, 115, 1.0],
offset: 0.45
},
{
color: [255, 255, 0, 0.5],
offset: 0.75
},
{
color: [255, 0, 0, 1.0],
offset: 1.0
}
];
function gradientMapper(grad: Gradient[]): MappedGradient {
const arr: number[] = [];
const gradLength = grad.length;
const offSetsArray: number[] = [];
grad.forEach(function (d) {
arr.push(d.color[0] / 255);
arr.push(d.color[1] / 255);
arr.push(d.color[2] / 255);
arr.push(d.color[3] === undefined ? 1.0 : d.color[3]);
offSetsArray.push(d.offset);
});
return {
value: new Float32Array(arr),
length: gradLength,
offset: offSetsArray
};
}
/**
* Generate vertex and intensity float32 data arrays
*/
function generateVertexData(projection: Projection, data: Point[]) {
const vertexData = new Float32Array(data.length * 2); // 2 floats per vertex x an y
for (let i = 0; i < data.length; i++) {
const {x, y} = projection.toWorldCoordinates(data[i].coordinates);
vertexData[i * 2] = x;
vertexData[i * 2 + 1] = y;
}
return {vertexData, intensityData: new Float32Array(data.map((p) => p.value))};
}
import type {MMapLocationRequest} from '@mappable-world/mappable-types';
export const LOCATION: MMapLocationRequest = {
center: [55.2744, 25.1972], // starting position [lng, lat]
zoom: 7 // starting zoom
};