UNPKG

itowns

Version:

A JS/WebGL framework for 3D geospatial data visualization

245 lines (229 loc) 12.1 kB
import { VIEW_EVENTS } from 'itowns'; import Widget from "./Widget.js"; const DEFAULT_OPTIONS = { displayCompass: true, display3DToggle: true, displayZoomIn: true, displayZoomOut: true, animationDuration: 500, position: 'bottom-left', direction: 'column' }; const DEFAULT_BUTTONS = { compass: { id: 'compass', content: '', info: 'Rotate the camera to face North', parentId: 'widgets' }, toggle3D: { id: '3d-button', content: '3D', info: 'Tilt the camera' }, zoomIn: { id: 'zoom-in-button', content: '<span class="widget-zoom-button-logo"></span>', info: 'Zoom in', parentId: 'zoom-button-bar' }, zoomOut: { id: 'zoom-out-button', content: '<span id="zoom-out-logo" class="widget-zoom-button-logo"></span>', info: 'Zoom out', parentId: 'zoom-button-bar' } }; /** * A widget menu manager for navigation. * * To use it, you need to link the widgets' stylesheet to your html webpage. This stylesheet is included in * [itowns bundles](https://github.com/iTowns/itowns/releases) if you downloaded them, or it can be found in * `node_modules/itowns/examples/css` if you installed iTowns with npm. Otherwise, it can be found at * [this link](https://raw.githubusercontent.com/iTowns/itowns/master/examples/css/widgets.css). See * [this example](http://www.itowns-project.org/itowns/examples/#widgets_navigation) for more details. * * @extends Widget * * @property {HTMLElement} domElement An html div containing all navigation widgets. * @property {HTMLElement} parentElement The parent HTML container of `this.domElement`. * @property {HTMLButtonElement} compass The HTML button for the compass. * @property {HTMLButtonElement} toggle3D The HTML button for the 3D/2D toggle button. * @property {HTMLButtonElement} zoomIn The HTML button for the zoom-in button. * @property {HTMLButtonElement} zoomOut The HTML button for the zoom-out button. * * @example * // Create a Navigation widget in the bottom-right corner of an iTowns view domElement * const navigation = new Navigation(view, { position: 'bottom-right' }; * * // Change the tooltip for the compass : * navigation.compass.title = 'new tooltip'; * * // Change the method ran when clicking zoom-in button : * function newMethod() { * // do something * } * navigation.zoomIn.onclick = newMethod; */ class Navigation extends Widget { #_view; #_action(params) { params.time = this.animationDuration; return this.#_view.controls.lookAtCoordinate(params); } #_addDefaultButton(settings, onclick) { return this.addButton(settings.id, settings.content, settings.info, onclick, settings.parentId); } /** * @param {GlobeView} view The iTowns view the navigation should be linked * to. For the moment, only `{@link GlobeView}` * is supported. * @param {Object} options The navigation menu optional configuration. * @param {HTMLElement} [options.parentElement=view.domElement] The parent HTML container of the div which * contains navigation widgets. * @param {boolean} [options.displayCompass=true] Whether the compass widget should be displayed. * @param {boolean} [options.display3DToggle=true] Whether the navigation should include a widget * to toggle between top and oblique view. * @param {boolean} [options.displayZoomIn=true] Whether the zoom-in widget should be displayed. * @param {boolean} [options.displayZoomOut=true] Whether the zoom-out widget should be displayed. * @param {number} [options.animationDuration=500] The duration of travel animations, when clicking * navigation widgets. * @param {string} [options.position='bottom-left'] Defines which corner of the `parentElement` the * navigation menu should be displayed to. * Possible values are `top-left`, `top-right`, * `bottom-left` and `bottom-right`. If the input * value does not match one of these, it will be * defaulted to `bottom-left`. * @param {string} [options.direction='column'] Whether the navigation menu should expand * horizontally or vertically. Possible values * are `column` and `row`. If the input value * does not match one of these, it will be * defaulted to `column`. * @param {Object} [options.translate] An optional translation of the navigation menu. * @param {number} [options.translate.x=0] The navigation menu translation along the page * x-axis. * @param {number} [options.translate.y=0] The navigation menu translation along the page * y-axis. */ constructor(view) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; // ---------- BUILD PROPERTIES ACCORDING TO DEFAULT OPTIONS AND OPTIONS PASSED IN PARAMETERS : ---------- // Check if the view is supported. if (!view.isGlobeView) { throw new Error('\'Navigation\' plugin only supports \'GlobeView\'. Therefore, the \'view\' parameter must be a ' + '\'GlobeView\'.'); } // `top`, `bottom`, `left` and `right` values for `position` option are not relevant for navigation widget. if (['top', 'bottom', 'left', 'right'].includes(options.position)) { console.warn('\'position\' optional parameter for \'Navigation\' is not a valid option. ' + `It will be set to '${DEFAULT_OPTIONS.position}'.`); options.position = DEFAULT_OPTIONS.position; } super(view, options, DEFAULT_OPTIONS); this.#_view = view; this.direction = options.direction || DEFAULT_OPTIONS.direction; if (!['column', 'row'].includes(this.direction)) { console.warn('\'direction\' optional parameter for \'Navigation\' constructor is not a valid option. ' + `It will be set to '${DEFAULT_OPTIONS.direction}'.`); this.direction = DEFAULT_OPTIONS.direction; } this.animationDuration = options.animationDuration === undefined ? DEFAULT_OPTIONS.animationDuration : options.animationDuration; // ---------- CREATE A DomElement WITH id AND classes RELEVANT TO THE WIDGET PROPERTIES : ---------- // Create a div containing all widgets and add it to its specified parent. this.domElement.id = 'widgets-navigation'; // Position widget div according to options. this.domElement.classList.add(`${this.direction}-widget`); // ---------- CREATE THE DEFAULT WIDGETS IF NOT HIDDEN (COMPASS, 3D AND ZOOM BUTTONS) : ---------- // Add a compass widget if requested. if (options.displayCompass ?? DEFAULT_OPTIONS.displayCompass) { this.compass = this.#_addDefaultButton(DEFAULT_BUTTONS.compass, () => { this.#_action({ heading: 0, tilt: 89.5 }); }); // Manage compass rotation when the view's camera is moved. view.addEventListener(VIEW_EVENTS.CAMERA_MOVED, event => { this.compass.style.transform = `rotate(${-event.heading}deg)`; }); } // Add a 3D toggle button if requested. if (options.display3DToggle ?? DEFAULT_OPTIONS.display3DToggle) { this.toggle3D = this.#_addDefaultButton(DEFAULT_BUTTONS.toggle3D, () => { this.#_action({ tilt: this.#_view.controls.getTilt() < 89 ? 89.5 : 40 }); }); // Manage button content toggle when the view's camera is moved. view.addEventListener(VIEW_EVENTS.CAMERA_MOVED, event => { this.toggle3D.innerHTML = event.tilt < 89 ? '2D' : '3D'; }); } // Add a zoom-in button if requested. if (options.displayZoomIn ?? DEFAULT_OPTIONS.displayZoomIn) { this.zoomIn = this.#_addDefaultButton(DEFAULT_BUTTONS.zoomIn, () => { this.#_action({ zoom: Math.min(20, this.#_view.controls.getZoom() + 1) }); }); } // Add a zoom-out button if requested. if (options.displayZoomOut ?? DEFAULT_OPTIONS.displayZoomOut) { this.zoomOut = this.#_addDefaultButton(DEFAULT_BUTTONS.zoomOut, () => { this.#_action({ zoom: Math.max(3, this.#_view.controls.getZoom() - 1) }); }); } } /** * * @param {string} id The unique id the created button should be given. * @param {string} [content=''] An HTML string defining the content of the button. * @param {string} [title=''] An HTML string defining information on the button. This string will be * displayed in a tooltip when hovering the button. * @param {function} [onclick] The method that should be executed when the button is clicked on. * @param {string} [parentId] The unique id of a button bar in which the created button should be added. * A button bar is a group which contains one or several buttons. All * buttons created with Navigation are in a button bar. If the given id does * not match an already existing button bar, a new button bar will be created * with this id. If no id is given, a button bar will be created with no id. * The later case can be useful for creating isolated buttons. * * @returns {HTMLButtonElement} The created button. */ addButton(id) { let content = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; let title = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; let onclick = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : () => {}; let parentId = arguments.length > 4 ? arguments[4] : undefined; let buttonBar = document.getElementById(parentId); if (!buttonBar) { buttonBar = this.addButtonBar(parentId); } const button = document.createElement('button'); button.className = 'widget-button'; button.id = id; button.innerHTML = content; button.title = title; button.onclick = onclick; buttonBar.appendChild(button); // The buttons must not be focused using tab key. button.tabIndex = -1; // When releasing the mouse after clicking the button, we give the focus back to the view. Therefore, we can use // key events on the view without having to click the view to grant it focus. window.addEventListener('pointerup', () => { if (document.activeElement === button) { this.#_view.domElement.focus(); } }); return button; } addButtonBar(id) { const buttonBar = document.createElement('div'); buttonBar.className = 'widget-button-bar'; if (id) { buttonBar.id = id; } this.domElement.appendChild(buttonBar); return buttonBar; } } export default Navigation;