GeotiefExplore/src/js/main.js
Arno Kaimbacher 0ba91f51c1 - border slicing via graphical overlay
- npm updates
2021-06-29 11:06:20 +02:00

551 lines
21 KiB
JavaScript

import { DirectionalLight } from 'three/src/lights/DirectionalLight';
import { AmbientLight } from 'three/src/lights/AmbientLight';
import { WebGLRenderer } from 'three/src/renderers/WebGLRenderer';
import { Scene } from 'three/src/scenes/Scene';
import { Vector3 } from 'three/src/math/Vector3';
import { GridLayer } from './layer/GridLayer';
// import { DemLayer } from './layer/DemLayer';
import { Map } from './core/Map';
import * as domEvent from './core/domEvent';
import { Coordinates } from './controls/Coordinates';
import { NorthArrow } from './controls/NorthArrow';
import { LayerControl } from './controls/LayerControl';
import { BasemapControl } from './controls/BasemapControl';
import { SliderControl } from './controls/SliderControl';
import { Mesh } from 'three/src/objects/Mesh';
import { SphereGeometry } from 'three/src/geometries/SphereGeometry';
import { MeshLambertMaterial } from 'three/src/materials/MeshLambertMaterial';
import * as util from './core/utilities';
import * as browser from './core/browser';
import * as domUtil from './core/domUtil';
import { PickingTool } from './clip/PickingTool';
import { ShowModal } from './components/ShowModal';
import * as material from './clip/material';
import { Group } from 'three/src/objects/Group';
import { Selection } from './clip/Selection';
import _ from "lodash";
import '../css/page_bulma.scss'; /* style loader will import it */
import { TinLayer } from './layer/TinLayer';
class Application {
capsSceneArray;
constructor(container) {
this.container = container;
this.running = false;
this.wireframeMode = false;
this.canvas;
this._canvasImageUrl;
this.downloadButton;
this.showCaps = true;
this.objects = [];
if (container.clientWidth && container.clientHeight) {
this.width = container.clientWidth;
this.height = container.clientHeight;
this._fullWindow = false;
} else {
this.width = window.innerWidth;
this.height = window.innerHeight;
this._fullWindow = true;
}
// this.canvas = document.querySelector('#imgCanvas');
// this.$topTextInput = document.querySelector('#topText');
// this.$bottomTextInput = document.querySelector('#bottomText');
// this.$imageInput = document.querySelector('#image');
this.downloadButton = document.querySelector('#menu-dowload-button');
this.menuIcon = document.querySelector('#menu-icon');
this.navigation = document.getElementsByClassName('navigation')[0];
let parentContainer = document.getElementById("app");
this.dialog = new ShowModal("Help", parentContainer, { klass: "fm_about" });
// this.dialog = new MobileDialog("Help", container, { klass: "fm_about" });
this.aboutIcon = document.querySelector('#menu-about-icon');
// this.createScene();
// this.addEventListeners();
}
async build() {
await this.createScene();
this.addEventListeners();
// add matomo code if defined in .env file:
if (ENVIRONMENT == "production" && MATOMO_TRACKER_URL != null && MATOMO_SITE_ID != null) {
let _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function () {
let u = MATOMO_TRACKER_URL;
_paq.push(['setTrackerUrl', u + 'matomo.php']);
_paq.push(['setSiteId', MATOMO_SITE_ID]);
let d = document; let g = d.createElement('script');
let s = d.getElementsByTagName('script')[0];
g.type = 'text/javascript';
g.async = true;
g.src = u + 'matomo.js';
s.parentNode.insertBefore(g, s);
})();
}
}
async createScene() {
let dirNode = document.getElementsByTagName("body")[0];
if (browser.touch == true && browser.mobile == true) {
//dirNode.setAttribute("dir", "ltr");
domUtil.addClass(dirNode, "touch");
} else {
domUtil.addClass(dirNode, "notouch");
}
// let opt = { r: 200, c: 0x38eeff, o: 0.8 };
let opt = { r: 5, c: 0xffff00, o: 1 };
this.queryMarker = new Mesh(new SphereGeometry(opt.r),
new MeshLambertMaterial({ color: opt.c, opacity: opt.o, transparent: (opt.o < 1) }));
this.queryMarker.visible = true;
// this.queryMarker.position.set(4282010, 2302070, -13616.3);
/* Renderer */
this.renderer = new WebGLRenderer({ alpha: true, antialias: true, preserveDrawingBuffer: true });
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(this.width, this.height);
this.renderer.autoClear = false;
this.renderer.setClearColor(0x000000, 0.0); // second param is opacity, 0 => transparent
// this.renderer.setClearColor( 0xffffff );
// enable clipping
// let Empty = Object.freeze([]);
// this.renderer.clippingPlanes = Empty; // GUI sets it to globalPlanes
this.renderer.localClippingEnabled = true;
this.container.appendChild(this.renderer.domElement);
/* Scene: that will hold all our elements such as objects, cameras and lights. */
this.scene = new Scene();
this.capsScene = new Scene();
this.backStencil = new Scene();
this.frontStencil = new Scene();
this.capsSceneArray = new Array();
this._buildDefaultLights(this.scene);
//app.scene.autoUpdate = false;
//// show axes in the screen
//app.scene.add(new THREE.AxisHelper(100));
this.scene.add(this.queryMarker);
/* Camera */
var angle = 45;
var aspect = this.width / this.height;
var near = 0.1; //This is the distance at which the camera will start rendering scene objects
var far = 2000; //Anything beyond this distance will not be rendered
// this.camera = new PerspectiveCamera(angle, aspect, near, far);
// this.camera.position.set(0, -0.1, 150);
// this.camera.lookAt(new Vector3(0, 0, 0));
// this.camera = new PerspectiveCamera(30, this.width / this.height, 100, 100000);
// let x = { min: 4415940, max: 4508490, avg: 4463830 };
// let y = { min: 2350280, max: 2475820, avg: 2412360 };
// let z = { min: -8798.15, max: 1401.92, avg: -177.74 };
// const center = new Vector3((x.min + x.max) / 2, (y.min + y.max) / 2, 0);
// const size = Math.max(x.max - x.min, y.max - y.min, z.max - z.min);
// let baseExtent = {
// x: x,
// y: y
// };
// const camDirection = new Vector3(-0.5, -Math.SQRT1_2, 0.5);
// // const camDirection = new Vector3(0, 0, 1);
// const camOffset = camDirection.multiplyScalar(size * 2);
// this.camera.position.copy(center);
// this.camera.position.add(camOffset);
// this.camera.near = size * 0.1;
// this.camera.far = size * 25;
// this.camera.updateProjectionMatrix();
/* Camera */
// // const center = new Vector3();
// var angle = 45;
// var aspect = this.width / this.height;
// var near = 0.1; //This is the distance at which the camera will start rendering scene objects
// var far = 2000; //Anything beyond this distance will not be rendered
// this.camera = new PerspectiveCamera(angle, aspect, near, far);
// this.camera.position.set(0, -0.1, 150);
// this.camera.lookAt(new Vector3(0, 0, 0));
let map = this.map = await Map.build(
this.scene,
this.container,
'https://geusegdi01.geus.dk/meta3d/rpc/model_meta_all?modelid=20'
);
this.mapTitle = document.querySelector('#map-title');
this.mapTitle.innerHTML += map.title;
map.on('ready', () => {
this.selectionBox.setUniforms();
// this.capsScene.add(this.selectionBox.boxMesh);
// this.scene.add(this.selection.displayMeshes);
// this.scene.add(this.selection.touchMeshes);
this.map.addLayer(this.selectionBox);
let profileNode = new Group();
for (const [key, layer] of Object.entries(this.map.layers)) {
// let layer = map.layers[i];
if (layer instanceof TinLayer && layer.name != "Topography") {
// this.capsScene.add(layer.borderMesh);
profileNode.add(layer.borderMesh);
layer.on('visibility-change', (args) => {
let visible = args[0];
layer.borderMesh.visible = visible;
});
layer.on('scale-change', (args) => {
let z = args[0];
layer.borderMesh.scale.z = z;
});
}
}
// this.scene.add(profileNode);
let stencilNode = new Group();
for (var i in map.layers) {
let layer = map.layers[i];
if (layer instanceof TinLayer && layer.name != "Topography") {
let stencilFeatureBack = new Mesh(layer.geometry, material.backStencilMaterial);
stencilFeatureBack.name = 'stencilFeatureBack_' + i;
stencilFeatureBack.userData.layerId = layer.index;
stencilNode.add(stencilFeatureBack);
let stencilFeatureFront = new Mesh(layer.geometry, material.frontStencilMaterial);
stencilFeatureFront.name = 'stencilFeatureFront_' + i;
stencilFeatureFront.userData.layerId = layer.index;
stencilNode.add(stencilFeatureFront);
layer.on('visibility-change', (args) => {
let visible = args[0];
stencilFeatureFront.visible = visible;
stencilFeatureBack.visible = visible;
});
layer.on('scale-change', (args) => {
let z = args[0];
stencilFeatureFront.scale.z = z;
stencilFeatureBack.scale.z = z;
});
}
}
// scene.add(node('selectNode'));
// scene.add(node('modelNode'));
this.scene.add(stencilNode);
this.scene.add(profileNode);
this.animate();
}, this);
this.selectionBox = new Selection(
{ name: 'Slicing Box' },
new Vector3(this.map.x.min, this.map.y.min, this.map.z.min),
new Vector3(this.map.x.max, this.map.y.max, this.map.z.max)
);
// this.map.addLayer(this.selectionBox);
this.map.picking = new PickingTool(this.map.size, this.map.center, this);
// let boxLayer = new BoxLayer({
// width: 10000, height: 10000, depth: 10000, name: 'center-box', color: 800080 , center: center
// });
// this.map.addLayer(boxLayer);
//add map controls:
if (util.hasTouch() == false) {
new Coordinates({ camera: this.map.camera, crs: "EPSG:3034" }).addTo(this.map);
// coordinates.addListener('onPoint', (args) => {
// let vector = args[0];
// this.queryMarker.position.set(vector.x, vector.y, vector.z);
// // this.queryMarker.updateMatrixWorld();
// this.animate();
// }, this);
}
this.northArrow = new NorthArrow({ headLength: 1, headWidth: 1 }).addTo(this.map);
this.gridlayer = new GridLayer({ center: this.map.center, name: "coordinate grid", appWidth: this.width, appHeight: this.height });
this.map.addLayer(this.gridlayer);
new LayerControl(this.map.layers, {
collapsed: true,
parentDiv: 'layer-control-parent-id'
}).addTo(this.map);
this.basemapControl = new BasemapControl('Baselayer', {
position: 'topright'
}).addTo(this.map);
//slider for scaling z value
this.slider = new SliderControl({ layers: this.map.layers }).addTo(this.map);
this.start();
}
keydown(e) {
if (e.ctrlKey || e.altKey) return;
let keyPressed = e.which;
if (!e.shiftKey) {
//if (keyPressed == 27) app.closePopup(); // ESC
if (keyPressed === 87) {
this.setWireframeMode(); // W
}
}
}
setWireframeMode() {
let wireframe = !this.wireframeMode;
if (wireframe === this.wireframeMode) return;
for (var key in this.map._layers) {
let layer = this.map._layers[key];
layer.setWireframeMode(wireframe);
}
this.wireframeMode = wireframe;
this.animate();
}
onWindowResize() {
if (this._fullWindow) {
this._setCanvasSize(window.innerWidth, window.innerHeight);
}
else {
this._setCanvasSize(this.container.clientWidth, this.container.clientHeight);
}
}
_setCanvasSize(width, height) {
this.width = width;
this.height = height;
this.map.camera.aspect = width / height;
this.map.camera.updateProjectionMatrix();
this.renderer.setSize(width, height);
this.animate();
}
_buildDefaultLights(scene) {
let deg2rad = Math.PI / 180;
// ambient light
scene.add(new AmbientLight(0x999999));
//scene.add(new THREE.AmbientLight(0xeeeeee));
// directional lights
let opt = {
azimuth: 220, // note: default light azimuth of gdaldem hillshade is 315.
altitude: 45 // altitude angle
};
//appSettings.Options.light.directional;
let lambda = (90 - opt.azimuth) * deg2rad;
let phi = opt.altitude * deg2rad;
let x = Math.cos(phi) * Math.cos(lambda),
y = Math.cos(phi) * Math.sin(lambda),
z = Math.sin(phi);
let light1 = new DirectionalLight(0xffffff, 0.5);
light1.position.set(x, y, z);
scene.add(light1);
// thin light from the opposite direction
let light2 = new DirectionalLight(0xffffff, 0.1);
light2.position.set(-x, -y, -z);
scene.add(light2);
}
start() {
this.running = true;
this.map.addListener('change', this.animate, this); // add this only if there is no animation loop (requestAnimationFrame)
this.animate();
}
deferringThrottle = _.throttle(this.animate, 40);
animate() {
this.renderer.clear();
// The HTML5 Canvas's 'webgl' context obtained from the canvas where the renderer will draw.
let gl = this.renderer.getContext();
// if (this.showCaps && gl != undefined) {
// // enable stencil test
// gl.enable(gl.STENCIL_TEST);
// // for (let i in this.map.layers) {
// // let layer = this.map.layers[i];
// // if (layer instanceof TinLayer && layer.name != "Topography") {
// // layer.animate();
// // break;
// // }
// // }
// gl.stencilFunc(gl.ALWAYS, 1, 0xff);
// gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR);
// this.renderer.render(this.backStencil, this.map.camera);
// gl.stencilFunc(gl.ALWAYS, 1, 0xff);
// gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR);
// this.renderer.render(this.frontStencil, this.map.camera);
// gl.stencilFunc(gl.EQUAL, 1, 0xff);
// gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
// this.renderer.render(this.capsScene, this.map.camera);
// // disable stencil test
// gl.disable(gl.STENCIL_TEST);
// // gl.stencilMask(0);
// // this.renderer.state.setStencilFunc( false );
// }
this.renderer.render(this.scene, this.map.camera);
this.northArrow.animate();
this.gridlayer.animate();
}
addEventListeners() {
domEvent.on(window, 'resize', this.onWindowResize, this);
domEvent.on(window, 'keydown', this.keydown, this);
// let inputNodes = [this.$topTextInput, this.$bottomTextInput, this.$imageInput];
// inputNodes.forEach(element => domEvent.on(element, 'keyup', this.createMeme, this));
// //if image is changed
// inputNodes.forEach(element => domEvent.on(element, 'change', this.createMeme, this));
domEvent.on(this.downloadButton, 'click', this.downloadMapImage, this);
domEvent.on(this.aboutIcon, 'click', function (e) {
e.preventDefault();
this.dialog.show();
}, this);
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
// Check if there are any navbar burgers
if ($navbarBurgers.length > 0) {
// Add a click event on each of them
$navbarBurgers.forEach(el => {
el.addEventListener('click', () => {
// Get the target from the "data-target" attribute
const target = el.dataset.target;
const $target = document.getElementById(target);
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
el.classList.toggle('is-active');
$target.classList.toggle('is-active');
});
});
}
let tabButtons = [].slice.call(document.querySelectorAll('ul.tab-nav li'));
tabButtons.map(function (button) {
button.addEventListener('click', function () {
document.querySelector('li.is-active').classList.remove('is-active');
button.classList.add('is-active');
document.querySelector('.tab-pane.active').classList.remove('active');
document.querySelector(button.getAttribute('name')).classList.add('active');
})
});
//toggle GridLayer
let chkGrid = document.getElementById("chkGrid");
domEvent.on(chkGrid, 'click', function (e) {
this.gridlayer.toggle();
}, this);
//toggle SlicingBox
let chkSlicingBox = document.getElementById("chkSlicingBox");
domEvent.on(chkSlicingBox, 'click', function (e) {
this.selectionBox.toggle();
}, this);
}
downloadMapImage() {
this.saveCanvasImage(this.renderer.domElement);
}
saveCanvasImage(canvas) {
// !HTMLCanvasElement.prototype.toBlob
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement.toBlob
// decode the String
var binStr = atob(canvas.toDataURL("image/png").split(',')[1]);
var len = binStr.length;
var arr = new Uint8Array(len);
for (var i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i);
}
this.saveBlob(new Blob([arr], { type: "image/png" }));
}
saveBlob(blob) {
// ie
if (window.navigator.msSaveBlob !== undefined) {
window.navigator.msSaveBlob(blob, filename);
//app.popup.hide();
}
else {
// create object url
if (this._canvasImageUrl) {
URL.revokeObjectURL(this._canvasImageUrl);
}
this._canvasImageUrl = URL.createObjectURL(blob);
// display a link to save the image
var e = this.downloadButton;//document.createElement("a");
e.href = this._canvasImageUrl;
}
}
createMeme() {
let context = this.$canvas.getContext('2d');
// font size of top and bottom text
let fontSize = ((this.canvas.width + this.canvas.height) / 2) * 4 / 100;
context.font = `${fontSize}pt sans-serif`;
context.textAlign = 'center';
context.textBaseline = 'top';
/**
* Stroke Text Style
*/
context.lineWidth = fontSize / 5;
context.strokeStyle = 'black';
/**
* Fill Text Style
*/
context.fillStyle = 'white';
// Fix lines over M
context.lineJoin = 'round';
// get he value of the top text an dbottom text from the input fields
let topText = this.$topTextInput.value.toUpperCase();
let bottomText = this.$bottomTextInput.value.toUpperCase();
// Top Text: first parameter text, second and thir parameters contain location where the text should start rendering
context.strokeText(topText, this.canvas.width / 2, this.canvas.height * (5 / 100));
context.fillText(topText, this.canvas.width / 2, this.canvas.height * (5 / 100));
// Bottom Text
context.strokeText(bottomText, this.canvas.width / 2, this.canvas.height * (90 / 100));
context.fillText(bottomText, this.canvas.width / 2, this.canvas.height * (90 / 100));
}
}
let container = document.getElementById("webgl");
let app = new Application(container);
app.build();