From ebdb23d089d87d33eb2e45e397f2136c9e8f62c0 Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Tue, 4 Apr 2017 16:49:34 +0200 Subject: [PATCH] Support for terrainProvider in Cesium - Now we can configure a terrainProvider - Support to intercept correct ray from camera to clicked point in feature info - defaultMapOptions added to localConfig as default. If present, it will be used to setup the mapOptions for the map. - Added to localConfig of product the demo terrainProvider - Throttling for mouse hover events in cesium map --- web/client/components/map/cesium/Map.jsx | 51 +++++++++++++------ .../map/cesium/__tests__/Map-test-chrome.jsx | 33 +++++++++--- web/client/localConfig.json | 9 ++++ web/client/plugins/Map.jsx | 8 +++ web/client/utils/cesium/ClickUtils.js | 51 +++++++++++++++++++ 5 files changed, 130 insertions(+), 22 deletions(-) create mode 100644 web/client/utils/cesium/ClickUtils.js diff --git a/web/client/components/map/cesium/Map.jsx b/web/client/components/map/cesium/Map.jsx index d68aa919f1..6555aaab22 100644 --- a/web/client/components/map/cesium/Map.jsx +++ b/web/client/components/map/cesium/Map.jsx @@ -9,7 +9,9 @@ var Cesium = require('../../../libs/cesium'); var React = require('react'); var ReactDOM = require('react-dom'); var ConfigUtils = require('../../../utils/ConfigUtils'); +var ClickUtils = require('../../../utils/cesium/ClickUtils'); var assign = require('object-assign'); +const {throttle} = require('lodash'); let CesiumMap = React.createClass({ propTypes: { @@ -55,7 +57,7 @@ let CesiumMap = React.createClass({ timeline: false, navigationHelpButton: false, navigationInstructionsInitiallyVisible: false - }, this.props.mapOptions)); + }, this.getMapOptions(this.props.mapOptions))); map.imageryLayers.removeAll(); map.camera.moveEnd.addEventListener(this.updateMapInfoState); this.hand = new Cesium.ScreenSpaceEventHandler(map.scene.canvas); @@ -63,19 +65,7 @@ let CesiumMap = React.createClass({ this.onClick(map, movement); }, Cesium.ScreenSpaceEventType.LEFT_CLICK); - this.hand.setInputAction((movement) => { - if (this.props.onMouseMove && movement.endPosition) { - const cartesian = map.camera.pickEllipsoid(movement.endPosition, map.scene.globe.ellipsoid); - if (cartesian) { - const cartographic = Cesium.Cartographic.fromCartesian(cartesian); - this.props.onMouseMove({ - y: cartographic.latitude * 180.0 / Math.PI, - x: cartographic.longitude * 180.0 / Math.PI, - crs: "EPSG:4326" - }); - } - } - }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); + this.hand.setInputAction(throttle(this.onMouseMove.bind(this), 500), Cesium.ScreenSpaceEventType.MOUSE_MOVE); map.camera.setView({ destination: Cesium.Cartesian3.fromDegrees( @@ -104,21 +94,22 @@ let CesiumMap = React.createClass({ this.map.destroy(); }, onClick(map, movement) { + if (this.props.onClick && movement.position !== null) { const cartesian = map.camera.pickEllipsoid(movement.position, map.scene.globe.ellipsoid); if (cartesian) { - const cartographic = Cesium.Cartographic.fromCartesian(cartesian); + let cartographic = ClickUtils.getMouseXYZ(map, movement) || Cesium.Cartographic.fromCartesian(cartesian); const latitude = cartographic.latitude * 180.0 / Math.PI; const longitude = cartographic.longitude * 180.0 / Math.PI; const y = ((90.0 - latitude) / 180.0) * this.props.standardHeight * (this.props.zoom + 1); const x = ((180.0 + longitude) / 360.0) * this.props.standardWidth * (this.props.zoom + 1); - this.props.onClick({ pixel: { x: x, y: y }, + height: this.props.mapOptions && this.props.mapOptions.terrainProvider ? cartographic.height : undefined, latlng: { lat: latitude, lng: longitude @@ -128,6 +119,34 @@ let CesiumMap = React.createClass({ } } }, + onMouseMove(movement) { + if (this.props.onMouseMove && movement.endPosition) { + const cartesian = this.map.camera.pickEllipsoid(movement.endPosition, this.map.scene.globe.ellipsoid); + if (cartesian) { + const cartographic = ClickUtils.getMouseXYZ(this.map, movement) || Cesium.Cartographic.fromCartesian(cartesian); + this.props.onMouseMove({ + y: cartographic.latitude * 180.0 / Math.PI, + x: cartographic.longitude * 180.0 / Math.PI, + crs: "EPSG:4326" + }); + } + } + }, + getMapOptions(rawOptions) { + let overrides = {}; + if (rawOptions.terrainProvider) { + let {type, ...tpOptions} = rawOptions.terrainProvider; + switch (type) { + case "cesium": { + overrides.terrainProvider = new Cesium.CesiumTerrainProvider(tpOptions); + break; + } + default: + break; + } + } + return assign({}, rawOptions, overrides); + }, getCenter() { const center = this.map.camera.positionCartographic; return { diff --git a/web/client/components/map/cesium/__tests__/Map-test-chrome.jsx b/web/client/components/map/cesium/__tests__/Map-test-chrome.jsx index 35e3ef7d29..4b504226e4 100644 --- a/web/client/components/map/cesium/__tests__/Map-test-chrome.jsx +++ b/web/client/components/map/cesium/__tests__/Map-test-chrome.jsx @@ -5,12 +5,12 @@ * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ -var React = require('react'); -var ReactDOM = require('react-dom'); -var CesiumMap = require('../Map.jsx'); -var CesiumLayer = require('../Layer.jsx'); -var expect = require('expect'); -var Cesium = require('../../../../libs/cesium'); +const React = require('react'); +const ReactDOM = require('react-dom'); +const CesiumMap = require('../Map.jsx'); +const CesiumLayer = require('../Layer.jsx'); +const expect = require('expect'); +const Cesium = require('../../../../libs/cesium'); require('../../../../utils/cesium/Layers'); require('../plugins/OSMLayer'); @@ -121,6 +121,27 @@ describe('CesiumMap', () => { ) }); }); + it('check mouse click handler', (done) => { + const testHandlers = { + handler: () => {} + }; + var spy = expect.spyOn(testHandlers, 'handler'); + + const map = ReactDOM.render( + + , document.getElementById("container")); + expect(map.map).toExist(); + map.onClick(map.map, {position: {x: 100, y: 100 }}); + setTimeout(() => { + expect(spy.calls.length).toEqual(1); + expect(spy.calls[0].arguments.length).toEqual(1); + done(); + }, 800); + }); it('check if the map changes when receive new props', () => { let map = ReactDOM.render( diff --git a/web/client/localConfig.json b/web/client/localConfig.json index f164d79c96..0e25a81f2e 100644 --- a/web/client/localConfig.json +++ b/web/client/localConfig.json @@ -12,6 +12,15 @@ "ignoreMobileCss": true, "useAuthenticationRules": true, "themePrefix": "ms2", + "defaultMapOptions": { + "cesium": { + "terrainProvider": { + "type": "cesium", + "url": "https://assets.agi.com/stk-terrain/world", + "requestVertexNormals": true + } + } + }, "authenticationRules": [{ "urlPattern": ".*geostore.*", "method": "basic" diff --git a/web/client/plugins/Map.jsx b/web/client/plugins/Map.jsx index 1ad394b0d0..6ad1fa7bce 100644 --- a/web/client/plugins/Map.jsx +++ b/web/client/plugins/Map.jsx @@ -14,6 +14,7 @@ const Spinner = require('react-spinkit'); require('./map/css/map.css'); const Message = require('../components/I18N/Message'); +const ConfigUtils = require('../utils/ConfigUtils'); const {isString} = require('lodash'); let plugins; /** @@ -122,6 +123,7 @@ const MapPlugin = React.createClass({ loadingError: React.PropTypes.string, tools: React.PropTypes.array, options: React.PropTypes.object, + mapOptions: React.PropTypes.object, toolsOptions: React.PropTypes.object, actions: React.PropTypes.object, features: React.PropTypes.array @@ -135,6 +137,7 @@ const MapPlugin = React.createClass({ loadingSpinner: true, tools: ["measurement", "locate", "overview", "scalebar", "draw", "highlight"], options: {}, + mapOptions: {}, toolsOptions: { measurement: {}, locate: {}, @@ -183,6 +186,10 @@ const MapPlugin = React.createClass({ } return tool[this.props.mapType] || tool; }, + getMapOptions() { + return this.props.mapOptions && this.props.mapOptions[this.props.mapType] || + ConfigUtils.getConfigProp("defaultMapOptions") && ConfigUtils.getConfigProp("defaultMapOptions")[this.props.mapType]; + }, renderLayers() { const projection = this.props.map.projection || 'EPSG:3857'; return this.props.layers.map((layer, index) => { @@ -224,6 +231,7 @@ const MapPlugin = React.createClass({ return ( {this.renderLayers()} diff --git a/web/client/utils/cesium/ClickUtils.js b/web/client/utils/cesium/ClickUtils.js new file mode 100644 index 0000000000..fd20d75849 --- /dev/null +++ b/web/client/utils/cesium/ClickUtils.js @@ -0,0 +1,51 @@ +/** + * Copyright 2017, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +var Cesium = require('../../libs/cesium'); +const getCartesian = function(viewer, event) { + if (event.position !== null) { + const scene = viewer.scene; + const ellipsoid = scene._globe.ellipsoid; + const cartesian = scene._camera.pickEllipsoid(event.position, ellipsoid); + return cartesian; + } +}; +const getMouseXYZ = (viewer, event) => { + var scene = viewer.scene; + if (!event.position) { + return null; + } + const ray = viewer.camera.getPickRay(event.position); + const position = viewer.scene.globe.pick(ray, viewer.scene); + const ellipsoid = scene._globe.ellipsoid; + if (Cesium.defined(position)) { + const cartographic = ellipsoid.cartesianToCartographic(position); + // const height = cartographic.height; + const cartesian = getCartesian(viewer, event); + if (cartesian) { + cartographic.height = scene._globe.getHeight(cartographic); + cartographic.cartesian = cartesian; + cartographic.position = position; + } + return cartographic; + } + return null; +}; + +const getMouseTile = (viewer, event) => { + const scene = viewer.scene; + if (!event.position) { + return null; + } + const ray = viewer.camera.getPickRay(event.position); + return viewer.scene.globe.pickTile(ray, scene); +}; + +module.exports = { + getMouseXYZ, + getMouseTile +};