src/Gisplay/Maps/Map.js
import { BGMapWrapper } from './BGMapWrapper';
import { Aesthetic } from '../Aesthetic';
/**
* This class contains the Map class which represents the current map.
* Each map has a group of functions available. There is only one map yet(maybe there will be two if we want to compare two).
*/
export class Map {
constructor(type, geometry, options) {
console.log("Map constructor called -> super()")
}
program() {
this._webgl.program = this._webgl.gl.createProgram();
this._webgl.heatmapProgram = [];
this._webgl.heatmapProgram[0] = this._webgl.gl.createProgram();
this._webgl.heatmapProgram[1] = this._webgl.gl.createProgram();
const source_code = this.generateShaders();
const vertex_shader = this.shader(this._webgl.gl.VERTEX_SHADER, source_code.vertex, this._webgl);
const fragment_shader = this.shader(this._webgl.gl.FRAGMENT_SHADER, source_code.fragment, this._webgl);
this._webgl.gl.attachShader(this._webgl.program, vertex_shader);
this._webgl.gl.attachShader(this._webgl.program, fragment_shader);
this._webgl.gl.linkProgram(this._webgl.program);
this._webgl.gl.useProgram(this._webgl.program);
}
shader(type, source_code, _webgl) { //Added HERE by Rui
var shader = _webgl.gl.createShader(type);
_webgl.gl.shaderSource(shader, source_code);
_webgl.gl.compileShader(shader);
console.log("shader " + (type.valueOf() == 35633 ? "vertex" : "fragment") + ": " + _webgl.gl.getShaderInfoLog(shader));
return shader;
}
generateShaders() { //Added HERE by Rui @TODO Refactor to use ES6 `` instead of +=
//general
let vertexSourceCode = " attribute vec4 vertexCoord; ";
vertexSourceCode += "\n attribute float aPointSize; ";
vertexSourceCode += "\n uniform mat4 projection; ";
vertexSourceCode += "\n attribute float a_opacity; ";
vertexSourceCode += "\n varying float v_opacity; ";
//vertexSourceCode+= "\n varying vec4 u_color; " ; //delete
vertexSourceCode += "\n void main() {";
vertexSourceCode += "\n gl_Position = (projection * vertexCoord); ";
vertexSourceCode += "\n gl_PointSize = aPointSize; v_opacity = a_opacity; ";
vertexSourceCode += "\n}";
let fragmentSourceCode = "precision mediump float;";
fragmentSourceCode += "\n uniform vec4 u_color;";//uniform
fragmentSourceCode += "\n varying float v_opacity; ";
fragmentSourceCode += "\n uniform float isPoint;";
fragmentSourceCode += "\n void main(){";
fragmentSourceCode += "\n float border = 0.5;";
fragmentSourceCode += "\n float radius = 0.5;";
fragmentSourceCode += "\n float centerDist = length(gl_PointCoord - 0.5);";
fragmentSourceCode += "\n float alpha;";
fragmentSourceCode += "\n if (u_color[3] == -1.0){"; //unnecessary??
fragmentSourceCode += "\n alpha = v_opacity * step(centerDist, radius);";//unnecessary??
fragmentSourceCode += "\n }";//unnecessary??
fragmentSourceCode += "\n else{";//unnecessary??
fragmentSourceCode += "\n alpha = u_color[3] * step(centerDist, radius);";
fragmentSourceCode += "\n }";//unnecessary??
fragmentSourceCode += "\n if(isPoint == 1.0 ){";
fragmentSourceCode += "\n if (alpha < 0.1) discard;";
fragmentSourceCode += "\n gl_FragColor = vec4(u_color[0], u_color[1], u_color[2], alpha);}";
fragmentSourceCode += "\n else";
fragmentSourceCode += "\n gl_FragColor = vec4(u_color[0], u_color[1], u_color[2], u_color[3]);";
fragmentSourceCode += "\n }";
return { vertex: vertexSourceCode, fragment: fragmentSourceCode };
}
addAesthetic(aes) {
this.aesthetics.push(aes);
}
setAesthetic(id, aes) {
for (let i = 0; i < aesthetics.length; i++) {
if (id == aesthetics[i].id) {
aesthetics[i] = aes;
break;
}
}
}
buildLegend() {
const mapCanvas = document.getElementById(`mapCanvas${this.id}`);
const legendDiv = document.createElement('div');
legendDiv.id = `legendDiv${this.id}`;
legendDiv.style.position = 'absolute';
legendDiv.style.backgroundColor = 'white';
//legendDiv.style.height = 200;//(mapCanvas.height / 10);
legendDiv.style.width = 250;//(mapCanvas.width / 10);
legendDiv.style.bottom = 20;
legendDiv.style.right = 0;
legendDiv.style.borderColor = 'black';
legendDiv.style.border = 'solid';
const table = document.createElement('table');
const thvalue = document.createElement('th');
const thcolor = document.createElement('th');
//thvalue.style.width = 125;
table.style.zIndex = "2000";
thcolor.style.width = 100;
table.appendChild(thcolor);
table.appendChild(thvalue);
for (const currentaes of this.aesthetics) {
//if(currentaes._features.length > 0 || currentaes._allFeatures.length > 0){
const row = document.createElement('tr');
const value = document.createElement('td');
const color = document.createElement('td');
const ptext = document.createElement('p');
let text;
if (typeof currentaes.range[0] === 'number')
text = document.createTextNode(`[${currentaes.range[0]}, ${currentaes.range[1]}[`);
else
text = document.createTextNode(currentaes.range[0]);
ptext.appendChild(text);
value.appendChild(ptext);
const colorDiv = document.createElement('div');
colorDiv.style.position = 'relative';
const rgbc = `rgba(${currentaes.fillColor[0]},${currentaes.fillColor[1]},${currentaes.fillColor[2]},${currentaes.fillColor[3]})`;
//console.log(rgbc);
colorDiv.style['backgroundColor'] = rgbc;
colorDiv.style.height = 25;//(mapCanvas.height / 10);
colorDiv.style.width = 80;//(mapCanvas.width / 10);
color.appendChild(colorDiv);
row.appendChild(color);
row.appendChild(value);
table.appendChild(row);
//}
}
legendDiv.appendChild(table);
this.map.getContainer().appendChild(legendDiv);
}
preProcessData(geojson, numberOf, algorithm, colorscheme) {
const aesarray = [];
const values = [];
const strings = [];
let breaks;
let fcolor;
for (let g = 0; g < geojson.features.length && (this.maxfeatures == undefined || g < this.maxfeatures); g++) {
if (geojson.features[g].properties[this.attr] != null && typeof geojson.features[g].properties[this.attr] == 'number') {
values.push(geojson.features[g].properties[this.attr]);
this.max = Math.max(this.max, geojson.features[g].properties[this.attr]);
this.min = Math.min(this.min, geojson.features[g].properties[this.attr]);
}
else
if (!strings.includes(geojson.features[g].properties[this.attr]))
strings.push(geojson.features[g].properties[this.attr]);
}
if (values.length > 0) {//quantitative
if (this.breaks == undefined) {
if (numberOf > 1)
breaks = this.calcClassBreaks(values, algorithm, numberOf);
else
breaks = [this.min, this.max];
}
else {
breaks = this.breaks;
}
if (breaks.length > 2) {
fcolor = chroma.scale(colorscheme).colors(breaks.length - 1);
for (var i = 0; i < breaks.length - 1; i++) {
var color = chroma(fcolor[i]).rgb();
if (i != breaks.length - 2) {
var aes = new Aesthetic(i, this.attr, [Math.round(color[0]), Math.round(color[1]), Math.round(color[2]), this.alpha], [0, 0, 0, 1], null, [breaks[i], breaks[i + 1]]);
}
else {
var aes = new Aesthetic(i, this.attr, [Math.round(color[0]), Math.round(color[1]), Math.round(color[2]), this.alpha], [0, 0, 0, 1], null, [breaks[i], breaks[i + 1]]);
aes.outer = true;
}
aesarray.push(aes);
}
}
else {
color = chroma(colorscheme[0]).rgb();
var aes = new Aesthetic(i, this.attr, [Math.round(color[0]), Math.round(color[1]), Math.round(color[2]), this.alpha], [0, 0, 0, 1], null, [breaks[0], breaks[1]]);
aes.outer = true;
aesarray.push(aes);
}
}
else {//qualitative
if (strings.length > 0) {
breaks = strings;
if (typeof colorscheme === 'string') {//string
fcolor = chroma.scale(colorscheme).colors(breaks.length);
}
else { //array
fcolor = chroma.scale(colorscheme).colors(breaks.length);
}
for (var i = 0; i < breaks.length; i++) {
var color = chroma(fcolor[i]).rgb();
var aes = new Aesthetic(i, this.attr, [Math.round(color[0]), Math.round(color[1]), Math.round(color[2]), 1], [0, 0, 0, 1], null, [strings[i]]);
aesarray.push(aes);
}
}
}
this.aesthetics = aesarray;
//return aesarray;
}
calcClassBreaks(values, algorithm, numberOf) {
let breaks;
switch (algorithm) {
case 'equidistant':
breaks = chroma.limits(values, 'e', numberOf);
break;
case 'quantile':
breaks = chroma.limits(values, 'q', numberOf);
break;
case 'k-means':
breaks = chroma.limits(values, 'k', numberOf);
break;
default:
breaks = chroma.limits(values, 'q', numberOf);
break;
}
return breaks;
}
/**
*
* @param {JSON} geojson
*/
processData(geojson) {
this.loadGeoJSON(geojson);
}
loadGeoJSON(geojson) {
for (let g = 0; g < geojson.features.length && (this.maxfeatures == undefined || g < this.maxfeatures); g++) {
geojson.features[g].properties['_gisplayid'] = g;
const geometry = geojson.features[g].geometry;
const properties = geojson.features[g].properties;
this.createAndInsertFeature(g, geometry, properties);
}
this.buildTrees(geojson);
}
createAndInsertFeature(id, geometry, properties) {
const gl = this._webgl.gl;
if (this.minuend != undefined && this.subtrahend != undefined && typeof properties[this.minuend] == 'number'
&& properties[this.subtrahend] != undefined && typeof properties[this.subtrahend] == 'number'
&& properties[this.subtrahend] != undefined) {
properties[this.attr] = properties[this.minuend] - properties[this.subtrahend];
}
if (geometry.type == "Polygon" || geometry.type == "MultiPolygon") {
this.hasPolygons = true;
const polygons = this.processPolygon({ geometry, properties });
const currentBorders = [];
const currentTriangles = [];
const bufferT = [];
const bufferB = [];
for (let j = 0; j < polygons.length; j++) {
const trianglespolygon = polygons[j].triangles;
const border = polygons[j].vertex;
currentTriangles[j] = new Array();
currentBorders[j] = new Array();
for (let h = 0; h < trianglespolygon.length; h++) {
var pixel = this.latLongToPixelXY(border[trianglespolygon[h] * 2], border[trianglespolygon[h] * 2 + 1]);
currentTriangles[j].push(pixel.x, pixel.y);
if (h == trianglespolygon.length - 1) {
bufferT.push(gl.createBuffer());
var vertArray = new Float32Array(currentTriangles[j]);
gl.fsize = vertArray.BYTES_PER_ELEMENT;
gl.bindBuffer(gl.ARRAY_BUFFER, bufferT[j]);
gl.bufferData(gl.ARRAY_BUFFER, vertArray, gl.STATIC_DRAW);
bufferT[j].itemSize = 2;
bufferT[j].numItems = vertArray.length / 2;
}
}
for (var y = 0; y < border.length; y += 2) {
var pixel = this.latLongToPixelXY(border[y], border[y + 1]);
currentBorders[j].push(pixel.x, pixel.y);
if (y == border.length - 2) {
bufferB.push(gl.createBuffer());
var vertArray = new Float32Array(currentBorders[j]);
gl.fsize = vertArray.BYTES_PER_ELEMENT;
gl.bindBuffer(gl.ARRAY_BUFFER, bufferB[j]);
gl.bufferData(gl.ARRAY_BUFFER, vertArray, gl.STATIC_DRAW);
bufferB[j].itemSize = 2;
bufferB[j].numItems = vertArray.length / 2;
}
}
}
//polygon
this.insertFeature(id, properties, bufferT, bufferB, []);
}
else if (geometry.type == "Point" && this.dynamic == true) {
//dum
const currentPoints = [];
currentPoints[0] = new Array();
var pixel = this.latLongToPixelXY(geometry.coordinates[0], geometry.coordinates[1]);
currentPoints[0].push(pixel.x, pixel.y);
const bufferP = [];
bufferP.push(gl.createBuffer());
var vertArray = new Float32Array(currentPoints[0]);
gl.fsize = vertArray.BYTES_PER_ELEMENT;
gl.bindBuffer(gl.ARRAY_BUFFER, bufferP[0]);
gl.bufferData(gl.ARRAY_BUFFER, vertArray, gl.STATIC_DRAW);
bufferP[0].itemSize = 2;
bufferP[0].numItems = vertArray.length / 2;
this.insertFeature(id, properties, [], [], bufferP);
if (this.treepoints == null || this.treepoints == undefined)
this.treepoints = [];
this.treepoints.push({ lon: geometry.coordinates[0], lat: geometry.coordinates[1], properties });
}
else if (geometry.type == "Point" && this.dynamic == false) {
//debugger;
var pixel = this.latLongToPixelXY(geometry.coordinates[0], geometry.coordinates[1]);
if (this.tempPoints == null || this.tempPoints == undefined) {
this.tempPoints = new Array();
for (let a = 0; a < this.aesthetics.length; a++) {
this.tempPoints[a] = [];
}
}
const aesarrays = this.fitFeature(properties);
for (var y = 0; y < aesarrays.length; y++) {
this.tempPoints[aesarrays[y]].push(pixel.x, pixel.y);
}
if (this.treepoints == null)
this.treepoints = [];
this.treepoints.push({ lon: geometry.coordinates[0], lat: geometry.coordinates[1], properties });
}
}
processPolygon(polygon) {
if (polygon.geometry.type == "Polygon") {
var outsidepolygon = polygon.geometry.coordinates[0];
var insidepolygons = [];
for (var k = 1; k < polygon.geometry.coordinates.length; k++) {
//todo inside polygon
//insidepolygons.push(polygon.geometry.coordinates[i][k]);
}
var tempVerts = new Array();
for (var out = 0; out < outsidepolygon.length - 1; out++) {
tempVerts.push(outsidepolygon[out][0], outsidepolygon[out][1]);
_vertexcount += (outsidepolygon.length + 1) / 2;
//console.log("lon: " + outsidepolygon[out][0] + " lat: " + outsidepolygon[out][1]);
}
var triangles_vert = earcut(tempVerts);
_tricount += (triangles_vert.length / 3);
polyarray.push({ triangles: triangles_vert, vertex: tempVerts });
}
else if (polygon.geometry.type == "MultiPolygon") {
var polyarray = [];
for (let i = 0; i < polygon.geometry.coordinates.length; i++) {
var outsidepolygon = polygon.geometry.coordinates[i][0];
var insidepolygons = [];
for (var k = 1; k < polygon.geometry.coordinates[i].length; k++) {
//todo inside polygon
insidepolygons.push(polygon.geometry.coordinates[i][k]);
}
var tempVerts = new Array();
_vertexcount += outsidepolygon.length;
for (var out = 0; out < outsidepolygon.length - 1; out++) {
tempVerts.push(outsidepolygon[out][0], outsidepolygon[out][1]);
//console.log("lon: " + outsidepolygon[out][0] + " lat: " + outsidepolygon[out][1]);
}
var triangles_vert = earcut(tempVerts);
//var temp = earcut.flatten(polygon.geometry.coordinates[i]);
//var triangles_vert = earcut(temp.vertices, temp.holes, temp.dimensions);
_tricount += (triangles_vert.length / 3);
polyarray.push({ triangles: triangles_vert, vertex: tempVerts });
//console.log(polyarray);
}
return polyarray;
}
}
insertFeature(id, properties, triangles, borders, points) {
let flag = false;
for (let i = 0; i < this.aesthetics.length; i++) {
if (this.aesthetics[i].checkProperty(properties[this.aesthetics[i].getAttr()]) == true) {
this.aesthetics[i].addFeature(id, properties, triangles, borders, points);
flag = true;
}
}
if (!flag) {
//TODO
//console.log("TODO: feature does not fit into any of the aesthetics defined.\n Value: " + properties[this.attr]);
}
}
latLongToPixelXY(longitude, latitude) {
const pi_180 = Math.PI / 180.0;
const pi_4 = Math.PI * 4;
const sinLatitude = Math.sin(latitude * pi_180);
const pixelY = (0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude)) / (pi_4)) * 256;
const pixelX = ((longitude + 180) / 360) * 256;
const pixel = { x: pixelX, y: pixelY };
return pixel;
}
fitFeature(properties) {
const result = [];
for (let a = 0; a < this.aesthetics.length; a++) {
if (this.aesthetics[a].checkProperty(properties[this.aesthetics[a].getAttr()]) == true)
result.push(a);
}
return result;
}
buildTrees(geojson) {
const gl = this._webgl.gl;
if (this.tempPoints != null) {
for (let t = 0; t < this.tempPoints.length; t++) {
if (this.tempPoints[t].length > 0) {
const bufferP = [];
bufferP.push(gl.createBuffer());
const vertArray = new Float32Array(this.tempPoints[t]);
gl.fsize = vertArray.BYTES_PER_ELEMENT;
gl.bindBuffer(gl.ARRAY_BUFFER, bufferP[0]);
gl.bufferData(gl.ARRAY_BUFFER, vertArray, gl.STATIC_DRAW);
bufferP[0].itemSize = 2;
bufferP[0].numItems = vertArray.length / 2;
this.insertGroupedFeature(t, [], [], bufferP);
}
}
}
//console.log(geojson)
if (this.treepoints != null)
this.kdtree = new kdTree(this.treepoints, (a, b) => (a.lon - b.lon) ** 2 + (a.lat - b.lat) ** 2, ["lon", "lat", "properties"]);
if (this.hasPolygons == true)
this.rtree = new PolygonLookup(geojson);
}
insertGroupedFeature(idaes, triangles, borders, points) {
this.aesthetics[idaes].addGroupedFeature(null, triangles, borders, points);
}
createCanvas() {
const canvas = this.map.createCanvas(this.id);
//init webgl properties
this._webgl = {
gl: null,
program: null,
projection: null
};
this._webgl.gl = canvas.getContext("webgl");
this._webgl.projection = new Float32Array(16);
this._webgl.projection.set([2 / canvas.width, 0, 0, 0, 0, -2 / canvas.height, 0, 0, 0, 0, 0, 0, -1, 1, 0, 1]);
this._webgl.gl.viewport(0, 0, this.map.getContainer().offsetWidth, this.map.getContainer().offsetHeight);
this._webgl.gl.disable(this._webgl.gl.DEPTH_TEST);
}
getNumberOfFeatures() {
let count = 0;
for (let i = 0; i < this.aesthetics.length; i++) {
count += this.aesthetics[i]._features.length;
}
return count;
}
scaleProjection(matrix, scaleX, scaleY) {
// scaling x and y, which is just scaling first two rows of matrix
matrix[0] *= scaleX;
matrix[1] *= scaleX;
matrix[2] *= scaleX;
matrix[3] *= scaleX;
matrix[4] *= scaleY;
matrix[5] *= scaleY;
matrix[6] *= scaleY;
matrix[7] *= scaleY;
}
translateProjection(matrix, tx, ty) {
// translation is in last row of matrix
matrix[12] += matrix[0] * tx + matrix[4] * ty;
matrix[13] += matrix[1] * tx + matrix[5] * ty;
matrix[14] += matrix[2] * tx + matrix[6] * ty;
matrix[15] += matrix[3] * tx + matrix[7] * ty;
}
clear() {
const gl = this._webgl.gl;
gl.clear(gl.COLOR_BUFFER_BIT);
gl.disable(gl.DEPTH_TEST);
}
drawTriangles(aes) {
const gl = this._webgl.gl;
if (gl == null) return;
const matrixProjection = new Float32Array(16);
//gl.clear(gl.COLOR_BUFFER_BIT);
//gl.disable(gl.DEPTH_TEST);
//gl.enable(gl.BLEND);
//gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
const currentZoom = this.map.getZoom();
const pointSize = Math.max(currentZoom - 5.0, 1.0);
matrixProjection.set(this._webgl.projection);
const scale = 2 ** currentZoom;
this.scaleProjection(matrixProjection, scale, scale);
const offset = this.latLongToPixelXY(this.map.getLngBound(), this.map.getLatBound());
this.translateProjection(matrixProjection, -offset.x, -offset.y);
const projectionLocation = gl.getUniformLocation(this._webgl.program, 'projection');
gl.uniformMatrix4fv(projectionLocation, false, matrixProjection);
const vertexSizeLocation = gl.getAttribLocation(this._webgl.program, 'aPointSize');
gl.vertexAttrib1f(vertexSizeLocation, pointSize);
const isPointLocation = gl.getUniformLocation(this._webgl.program, 'isPoint');
gl.uniform1f(isPointLocation, 0.0);
const vertexCoordLocation = gl.getAttribLocation(this._webgl.program, 'vertexCoord');
const vertexColorLocation = gl.getUniformLocation(this._webgl.program, "u_color");
/**
*
* Draw Polygons' Interior
* **/
const fsize = Float32Array.BYTES_PER_ELEMENT;
//console.log("Numero de Buffers: ", buffers.length);
gl.uniform4f(vertexColorLocation, aes.fillColor[0] / 255, aes.fillColor[1] / 255, aes.fillColor[2] / 255, aes.fillColor[3]);
for (let i = 0; i < aes._features.length; i++) {
for (let y = 0; y < aes._features[i]._triangles.length; y++) {
gl.bindBuffer(gl.ARRAY_BUFFER, aes._features[i]._triangles[y]);
gl.enableVertexAttribArray(vertexCoordLocation);
gl.vertexAttribPointer(vertexCoordLocation, 2, gl.FLOAT, false, fsize * 2, 0);
//gl.vertexAttribPointer(vertexColorLocation, 4, gl.FLOAT, false, fsize * 6, fsize * 2);
//gl.enableVertexAttribArray(vertexColorLocation);
gl.drawArrays(gl.TRIANGLES, 0, aes._features[i]._triangles[y].numItems);
}
}
}
drawBorders(aes) {
const gl = this._webgl.gl;
if (gl == null) return;
const matrixProjection = new Float32Array(16);
//gl.clear(gl.COLOR_BUFFER_BIT);
//gl.disable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
const currentZoom = this.map.getZoom();
const pointSize = Math.max(currentZoom - 5.0, 1.0);
matrixProjection.set(this._webgl.projection);
const scale = 2 ** currentZoom;
this.scaleProjection(matrixProjection, scale, scale);
const offset = this.latLongToPixelXY(this.map.getLngBound(), this.map.getLatBound());
this.translateProjection(matrixProjection, -offset.x, -offset.y);
const projectionLocation = gl.getUniformLocation(this._webgl.program, 'projection');
gl.uniformMatrix4fv(projectionLocation, false, matrixProjection);
const vertexSizeLocation = gl.getAttribLocation(this._webgl.program, 'aPointSize');
gl.vertexAttrib1f(vertexSizeLocation, pointSize);
const vertexCoordLocation = gl.getAttribLocation(this._webgl.program, 'vertexCoord');
const vertexColorLocation = gl.getUniformLocation(this._webgl.program, "u_color");
const isPointLocation = gl.getUniformLocation(this._webgl.program, 'isPoint');
gl.uniform1f(isPointLocation, 0.0);
/**
*
* Draw Polygons' Interior
* **/
const fsize = Float32Array.BYTES_PER_ELEMENT;
//console.log("Numero de Buffers: ", buffers.length);
gl.uniform4f(vertexColorLocation, aes.strokeColor[0] / 255, aes.strokeColor[1] / 255, aes.strokeColor[2] / 255, aes.strokeColor[3]);
for (let i = 0; i < aes._features.length; i++) {
for (let y = 0; y < aes._features[i]._borders.length; y++) {
gl.bindBuffer(gl.ARRAY_BUFFER, aes._features[i]._borders[y]);
gl.enableVertexAttribArray(vertexCoordLocation);
gl.vertexAttribPointer(vertexCoordLocation, 2, gl.FLOAT, false, fsize * 2, 0);
//gl.vertexAttribPointer(vertexColorLocation, 4, gl.FLOAT, false, fsize * 6, fsize * 2);
//gl.enableVertexAttribArray(vertexColorLocation);
gl.drawArrays(gl.LINE_LOOP, 0, aes._features[i]._borders[y].numItems);
}
}
}
drawPoints(aes) {
const gl = this._webgl.gl;
if (gl == null) return;
const matrixProjection = new Float32Array(16);
//gl.clear(gl.COLOR_BUFFER_BIT);
//gl.disable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
const currentZoom = this.map.getZoom();
const pointSize = Math.max(currentZoom - 4.0 + aes.pointSize, aes.pointSize);
matrixProjection.set(this._webgl.projection);
const scale = 2 ** currentZoom;
this.scaleProjection(matrixProjection, scale, scale);
const offset = this.latLongToPixelXY(this.map.getLngBound(), this.map.getLatBound());
this.translateProjection(matrixProjection, -offset.x, -offset.y);
const projectionLocation = gl.getUniformLocation(this._webgl.program, 'projection');
gl.uniformMatrix4fv(projectionLocation, false, matrixProjection);
const vertexSizeLocation = gl.getAttribLocation(this._webgl.program, 'aPointSize');
gl.vertexAttrib1f(vertexSizeLocation, pointSize);
const vertexCoordLocation = gl.getAttribLocation(this._webgl.program, 'vertexCoord');
const vertexColorLocation = gl.getUniformLocation(this._webgl.program, "u_color");
const isPointLocation = gl.getUniformLocation(this._webgl.program, 'isPoint');
gl.uniform1f(isPointLocation, 1.0);
/**
*
* Draw Polygons' Interior
* **/
const fsize = Float32Array.BYTES_PER_ELEMENT;
//console.log("Numero de Buffers: ", buffers.length);
gl.uniform4f(vertexColorLocation, aes.fillColor[0] / 255, aes.fillColor[1] / 255, aes.fillColor[2] / 255, aes.fillColor[3]);
for (var i = 0; i < aes._features.length && this.dynamic == true; i++) {
for (var y = 0; y < aes._features[i]._points.length; y++) {
gl.bindBuffer(gl.ARRAY_BUFFER, aes._features[i]._points[y]);
gl.enableVertexAttribArray(vertexCoordLocation);
gl.vertexAttribPointer(vertexCoordLocation, 2, gl.FLOAT, false, fsize * 2, 0);
//gl.vertexAttribPointer(vertexColorLocation, 4, gl.FLOAT, false, fsize * 6, fsize * 2);
//gl.enableVertexAttribArray(vertexColorLocation);
gl.drawArrays(gl.POINTS, 0, aes._features[i]._points[y].numItems);
//1);
}
}
for (var i = 0; this.dynamic == false && aes._allFeatures != null && i < aes._allFeatures.length; i++) {
for (var y = 0; y < aes._allFeatures[i]._points.length; y++) {
gl.bindBuffer(gl.ARRAY_BUFFER, aes._allFeatures[i]._points[y]);
gl.enableVertexAttribArray(vertexCoordLocation);
gl.vertexAttribPointer(vertexCoordLocation, 2, gl.FLOAT, false, fsize * 2, 0);
//gl.vertexAttribPointer(vertexColorLocation, 4, gl.FLOAT, false, fsize * 6, fsize * 2);
//gl.enableVertexAttribArray(vertexColorLocation);
gl.drawArrays(gl.POINTS, 0, aes._allFeatures[i]._points[y].numItems);
//gl.drawArrays(gl.TRIANGLE_STRIP, 0, aes._allFeatures[i]._points[y].numItems-2);
//1);
}
}
}
drawContinuousPolygons(aes) {
const gl = this._webgl.gl;
if (gl == null) return;
const matrixProjection = new Float32Array(16);
//gl.clear(gl.COLOR_BUFFER_BIT);
//gl.disable(gl.DEPTH_TEST);
//gl.enable(gl.BLEND);
//gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
const currentZoom = this.map.getZoom();
const pointSize = Math.max(currentZoom - 5.0, 1.0);
matrixProjection.set(this._webgl.projection);
const scale = 2 ** currentZoom;
this.scaleProjection(matrixProjection, scale, scale);
const offset = this.latLongToPixelXY(this.map.getLngBound(), this.map.getLatBound());
this.translateProjection(matrixProjection, -offset.x, -offset.y);
const projectionLocation = gl.getUniformLocation(this._webgl.program, 'projection');
gl.uniformMatrix4fv(projectionLocation, false, matrixProjection);
const vertexSizeLocation = gl.getAttribLocation(this._webgl.program, 'aPointSize');
gl.vertexAttrib1f(vertexSizeLocation, pointSize);
const isPointLocation = gl.getUniformLocation(this._webgl.program, 'isPoint');
gl.uniform1f(isPointLocation, 0.0);
const vertexCoordLocation = gl.getAttribLocation(this._webgl.program, 'vertexCoord');
const vertexColorLocation = gl.getUniformLocation(this._webgl.program, "u_color");
/**
*
* Draw Polygons' Interior
* **/
const fsize = Float32Array.BYTES_PER_ELEMENT;
//console.log("Numero de Buffers: ", buffers.length);
for (let i = 0; i < aes._features.length; i++) {
let ucolor;
let color;
const diff = aes._features[i]._properties[this.attr];
if (diff == 0)
color = aes.fillColor(0.5).rgb();
else {
if (diff > 0) {
color = aes.fillColor(0.5 + diff / this.max / 2).rgb();
} else {
color = aes.fillColor(0.5 - diff / this.min / 2).rgb();
}
}
ucolor = [Math.round(color[0]), Math.round(color[1]), Math.round(color[2]), this.alpha];
gl.uniform4f(vertexColorLocation, ucolor[0] / 255, ucolor[1] / 255, ucolor[2] / 255, this.alpha);
for (let y = 0; y < aes._features[i]._triangles.length; y++) {
gl.bindBuffer(gl.ARRAY_BUFFER, aes._features[i]._triangles[y]);
gl.enableVertexAttribArray(vertexCoordLocation);
gl.vertexAttribPointer(vertexCoordLocation, 2, gl.FLOAT, false, fsize * 2, 0);
//gl.vertexAttribPointer(vertexColorLocation, 4, gl.FLOAT, false, fsize * 6, fsize * 2);
//gl.enableVertexAttribArray(vertexColorLocation);
gl.drawArrays(gl.TRIANGLES, 0, aes._features[i]._triangles[y].numItems);
}
}
}
drawProporcionalPoints(aes) {
const gl = this._webgl.gl;
if (gl == null) return;
const matrixProjection = new Float32Array(16);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
const currentZoom = this.map.getZoom();
matrixProjection.set(this._webgl.projection);
const scale = 2 ** currentZoom;
this.scaleProjection(matrixProjection, scale, scale);
const offset = this.latLongToPixelXY(this.map.getLngBound(), this.map.getLatBound());
this.translateProjection(matrixProjection, -offset.x, -offset.y);
const projectionLocation = gl.getUniformLocation(this._webgl.program, 'projection');
gl.uniformMatrix4fv(projectionLocation, false, matrixProjection);
const vertexCoordLocation = gl.getAttribLocation(this._webgl.program, 'vertexCoord');
const vertexColorLocation = gl.getUniformLocation(this._webgl.program, "u_color");
const isPointLocation = gl.getUniformLocation(this._webgl.program, 'isPoint');
gl.uniform1f(isPointLocation, 1.0);
/**
*
* Draw Polygons' Interior
* **/
const fsize = Float32Array.BYTES_PER_ELEMENT;
//console.log("Numero de Buffers: ", buffers.length);
gl.uniform4f(vertexColorLocation, aes.fillColor[0] / 255, aes.fillColor[1] / 255, aes.fillColor[2] / 255, this.alpha);
if (this.dynamic == true) {
for (const i in aes._features) {
for (const y in aes._features[i]._points) {
gl.bindBuffer(gl.ARRAY_BUFFER, aes._features[i]._points[y]);
const propvalue = parseFloat(aes._features[i]._properties[this.attr]);
const temppointsize = ((this.maxpointsize - this.minpointsize) / (this.max - this.min)) * (propvalue - this.min);
const pointSize = Math.max(currentZoom - 4.0 + temppointsize * currentZoom / 4, 2);
const vertexSizeLocation = gl.getAttribLocation(this._webgl.program, 'aPointSize');
gl.vertexAttrib1f(vertexSizeLocation, pointSize);
gl.enableVertexAttribArray(vertexCoordLocation);
gl.vertexAttribPointer(vertexCoordLocation, 2, gl.FLOAT, false, fsize * 2, 0);
//gl.vertexAttribPointer(vertexColorLocation, 4, gl.FLOAT, false, fsize * 6, fsize * 2);
//gl.enableVertexAttribArray(vertexColorLocation);
gl.drawArrays(gl.POINTS, 0, aes._features[i]._points[y].numItems);
//1);
}
}
}
}
initialize() {
this.max = null;
this.min = null;
this.createCanvas();
this.program();
let mappos;
for (let i = 0; i < maps.length; i++)
if (maps[i].id == this.id)
mappos = i;
this.map.onEvent('move', () => {
console.log("@Rui: mudar o profiling do window para outra coisa?")
if (window.profiling == true)
var start = Date.now();
maps[mappos].draw();
if (window.profiling == true) {
const end = Date.now();
window.console.log(`Tempo de processamento de Zoom/Pan (segundos):${(end - start) / 1000}`);
}
}
);
this.setupOnclick(mappos);
}
setupOnclick(mappos) {
console.log("@Rui: profiling mudar de window para? | Rtree e KdTree")
maps[mappos].map.onEvent('click', e => {
if (window.profiling == true)
var start = Date.now();
const lat = e.latlng.lat;
const lon = e.latlng.lng;
if (maps[mappos].rtree != undefined) {
var bool = maps[0].rtree.search(lon, lat);
if (bool == undefined)
return;
else {
//console.log
var s = "";
var first = true;
if (maps[mappos].showPropertiesOnClick != null) {
for (var i = 0; i < maps[mappos].showPropertiesOnClick.length; i += 2) {
if (first) {
s += `${maps[mappos].showPropertiesOnClick[i + 1]}: ${bool.properties[maps[mappos].showPropertiesOnClick[i]]}`;
first = false;
}
else {
s += `\n${maps[mappos].showPropertiesOnClick[i + 1]}: ${bool.properties[maps[mappos].showPropertiesOnClick[i]]}`;
}
}
}
else {
var keys = Object.keys(bool.properties);
for (var i = 0; i < keys.length; i++) {
if (keys[i] != "_gisplayid") {
if (first) {
s += `${keys[i]}: ${bool.properties[keys[i]]}`;
first = false;
}
else {
s += `\n${keys[i]}: ${bool.properties[keys[i]]}`;
}
}
}
}
if (maps[mappos].interactive == true)
alert(s);//todo
if (maps[mappos].mapOnClickCall != undefined && maps[mappos].mapOnClickCall != null)
maps[mappos].mapOnClickCall(bool);
}
}
if (maps[mappos].kdtree != undefined) {
const nearest = maps[mappos].kdtree.nearest({ lat, lon }, 1, 128 / ((2 ** (map.getZoom() * 2))));
if (nearest.length <= 0)
return;
else {
var bool = nearest[0][0];
//console.log
var s = "";
var first = true;
if (maps[mappos].showPropertiesOnClick != null) {
for (var i = 0; i < maps[mappos].showPropertiesOnClick.length; i += 2) {
if (first) {
s += `${maps[mappos].showPropertiesOnClick[i + 1]}: ${bool.properties[maps[mappos].showPropertiesOnClick[i]]}`;
first = false;
}
else {
s += `\n${maps[mappos].showPropertiesOnClick[i + 1]}: ${bool.properties[maps[mappos].showPropertiesOnClick[i]]}`;
}
}
}
else {
var keys = Object.keys(bool.properties);
for (var i = 0; i < keys.length; i++) {
if (keys[i] != "_gisplayid") {
if (first) {
s += `${keys[i]}: ${bool.properties[keys[i]]}`;
first = false;
}
else {
s += `\n${keys[i]}: ${bool.properties[keys[i]]}`;
}
}
}
}
if (maps[mappos].interactive == true)
alert(s);
if (maps[mappos].mapOnClickCall != undefined && maps[mappos].mapOnClickCall != null)
maps[mappos].mapOnClickCall(bool);
}
}
if (window.profiling == true) {
const end = Date.now();
window.console.log(`Tempo de processamento de um click (segundos): ${(end - start) / 1000}`);
}
});
}
loadOptions(options, bgmap) {
if (options.customMapService == true)
this.map = bgmap;
else
this.map = new BGMapWrapper(bgmap);
if (options.loader != false)
this.loader();
if (options.showPropertiesOnClick == true) {
this.showPropertiesOnClick = null;
//append on bgmap object
}
else if (options.showPropertiesOnClick == false) {
//nada
}
else if (options.showPropertiesOnClick != undefined) {
this.showPropertiesOnClick = options.showPropertiesOnClick;
}
this.alpha = options.alpha != undefined ? options.alpha : 0.8;
this.interactive = options.interactive == undefined ? true : !options.interactive;
this.attr = options.attr;
this.dynamic = options.memorySaver == undefined ? false : !options.memorySaver;
this.maxfeatures = options.maxFeatures;
this.breaks = options.classBreaks;
this.colorscheme = options.colorScheme;
this.numberofclasses = options.numberOfClasses;
this.algorithm = options.classBreaksMethod;
this.legendOnClickCall = options.legendOnClickFunction;
this.mapOnClickCall = options.mapOnClickFunction;
this.minuend = options.minuend;
this.subtrahend = options.subtrahend;
this.legendTitle = options.legendTitle != undefined ? options.legendTitle : (this.attr != undefined ? this.attr : `${this.minuend} - ${this.subtrahend}`);
this.numberOfLegendItems = options.numberOfLegendItems != undefined ? options.numberOfLegendItems : 2;
}
loader() {
this.map.loader();
}
getNumberOfPolygons() {
let count = 0;
for (let i = 0; i < this.aesthetics.length; i++) {
for (let z = 0; z < this.aesthetics[i]._features.length; z++) {
count += this.aesthetics[i]._features[z]._triangles.length;
}
}
return count;
}
//STATIC METHODS(should be implemented by sub-classes)
draw() {
alert("draw() not implemented");//throw new NotImplementedError();
}
/**
* @deprecated This was created for Heat Maps. Another idea will probably be used.
* @param {Aesthetic} aes
*/
drawHeatPoints(aes) {
const gl = this._webgl.gl;
if (gl == null) return;
gl.useProgram(this._webgl.heatmapProgram[0]);
const matrixProjection = new Float32Array(16);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE);
const currentZoom = map.getZoom();
const pointSize = Math.max(currentZoom - 5.0, 1.0);
matrixProjection.set(this._webgl.projection);
const scale = 2 ** currentZoom;
this.scaleProjection(matrixProjection, scale, scale);
const offset = this.latLongToPixelXY(this.map.getLngBound(), this.map.getLatBound());
this.translateProjection(matrixProjection, -offset.x, -offset.y);
const projectionLocation = gl.getUniformLocation(this._webgl.heatmapProgram[0], 'projection');
gl.uniformMatrix4fv(projectionLocation, false, matrixProjection);
const vertexCoordLocation = gl.getAttribLocation(this._webgl.heatmapProgram[0], 'position');
const deltaLocation = gl.getAttribLocation(this._webgl.heatmapProgram[0], 'delta');
const intensityLoc = gl.getAttribLocation(this._webgl.heatmapProgram[0], 'intensity');
const vertexSizeLocation = gl.getAttribLocation(this._webgl.heatmapProgram[0], 'aPointSize');
gl.vertexAttrib1f(vertexSizeLocation, pointSize);
gl.enableVertexAttribArray(vertexCoordLocation);
gl.enableVertexAttribArray(deltaLocation);
gl.enableVertexAttribArray(intensityLoc);
const fsize = Float32Array.BYTES_PER_ELEMENT;
gl.bindBuffer(gl.ARRAY_BUFFER, aes._allFeatures[0]._points[0]);
gl.vertexAttribPointer(vertexCoordLocation, 2, gl.FLOAT, false, fsize * 8, 0 * 2);
gl.vertexAttribPointer(deltaLocation, 2, gl.FLOAT, false, fsize * 8, 2 * 4);
gl.vertexAttribPointer(intensityLoc, 4, gl.FLOAT, false, fsize * 8, 4 * 4);
console.log(aes._allFeatures[0]._points[0].numItems);
gl.drawArrays(gl.TRIANGLES, 0, aes._allFeatures[0]._points[0].numItems);
gl.useProgram(this._webgl.heatmapProgram[1]);
gl.disable(gl.BLEND);
//console.log("fase 1 concluida");
const canvas = document.getElementById(`mapCanvas${this.id}`);
const source = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, source);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
function isPowerOf2(value) {
return (value & (value - 1)) == 0;
};
function steupTextureFilteringAndMips(width, height, gl) {
if (isPowerOf2(width) && isPowerOf2(height)) {
// the dimensions are power of 2 so generate mips and turn on
// tri-linear filtering.
gl.generateMipmap(gl.TEXTURE_2D);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
} else {
// at least one of the dimensions is not a power of 2 so set the filtering
// so WebGL will render it.
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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
}
};
steupTextureFilteringAndMips(canvas.width, canvas.height, gl);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
const vertices = new Float32Array([1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1]);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const positionLoc = gl.getAttribLocation(this._webgl.heatmapProgram[1], 'position');
const sourceLoc = gl.getUniformLocation(this._webgl.heatmapProgram[1], 'source');
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
gl.uniform1i(sourceLoc, 0);
gl.drawArrays(gl.TRIANGLES, 0, 6);
gl.disableVertexAttribArray(positionLoc);
//defaults to general program
//console.log("fase 2 concluida");
gl.bindBuffer(gl.ARRAY_BUFFER, null);
this._webgl.gl.useProgram(this._webgl.program);
}
}