qwc2
Version:
QGIS Web Client
11 lines • 21.4 kB
JavaScript
function _typeof(o){"@babel/helpers - typeof";return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(o){return typeof o}:function(o){return o&&"function"==typeof Symbol&&o.constructor===Symbol&&o!==Symbol.prototype?"symbol":typeof o},_typeof(o)}function _classCallCheck(a,n){if(!(a instanceof n))throw new TypeError("Cannot call a class as a function")}function _defineProperties(e,r){for(var t=0;t<r.length;t++){var o=r[t];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,_toPropertyKey(o.key),o)}}function _createClass(e,r,t){return r&&_defineProperties(e.prototype,r),t&&_defineProperties(e,t),Object.defineProperty(e,"prototype",{writable:!1}),e}function _callSuper(t,o,e){return o=_getPrototypeOf(o),_possibleConstructorReturn(t,_isNativeReflectConstruct()?Reflect.construct(o,e||[],_getPrototypeOf(t).constructor):o.apply(t,e))}function _possibleConstructorReturn(t,e){if(e&&("object"==_typeof(e)||"function"==typeof e))return e;if(void 0!==e)throw new TypeError("Derived constructors may only return object or undefined");return _assertThisInitialized(t)}function _assertThisInitialized(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function _isNativeReflectConstruct(){try{var t=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(t){}return(_isNativeReflectConstruct=function _isNativeReflectConstruct(){return!!t})()}function _getPrototypeOf(t){return _getPrototypeOf=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)},_getPrototypeOf(t)}function _inherits(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),e&&_setPrototypeOf(t,e)}function _setPrototypeOf(t,e){return _setPrototypeOf=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},_setPrototypeOf(t,e)}function _defineProperty(e,r,t){return(r=_toPropertyKey(r))in e?Object.defineProperty(e,r,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[r]=t,e}function _toPropertyKey(t){var i=_toPrimitive(t,"string");return"symbol"==_typeof(i)?i:i+""}function _toPrimitive(t,r){if("object"!=_typeof(t)||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,r||"default");if("object"!=_typeof(i))return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===r?String:Number)(t)}/**
* Copyright 2024 Sourcepole AG
* 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.
*/import React from"react";import{connect}from"react-redux";import PropTypes from"prop-types";import{addLayer,addLayerFeatures,changeLayerProperty,removeLayer,LayerRole}from"../actions/layers";import{setCurrentTask}from"../actions/task";import ResizeableWindow from"../components/ResizeableWindow";import Spinner from"../components/widgets/Spinner";import CoordinatesUtils from"../utils/CoordinatesUtils";import LocaleUtils from"../utils/LocaleUtils";import MapUtils from"../utils/MapUtils";import ResourceRegistry from"../utils/ResourceRegistry";import"./style/Cyclomedia.css";var Status={LOGIN:0,INITIALIZING:1,INITIALIZED:2,ERROR:3,LOADPOS:4,HAVEPOS:5};/**
* Cyclomedia integration for QWC2.
*/var Cyclomedia=/*#__PURE__*/function(_React$Component){function Cyclomedia(props){var _this;_classCallCheck(this,Cyclomedia);_this=_callSuper(this,Cyclomedia,[props]);_defineProperty(_this,"state",{status:Status.LOGIN,message:"",username:"",password:"",loginFailed:false});_defineProperty(_this,"onClose",function(){_this.props.setCurrentTask(null);_this.setState({status:Status.LOGIN,loginFailed:false});_this.iframe=null});_defineProperty(_this,"setIframeRef",function(iframe){if(iframe&&iframe!==_this.iframe){_this.iframe=iframe;clearInterval(_this.iframePollIntervall);_this.iframePollIntervall=setInterval(function(){return _this.setupIframe(iframe)},500)}});_defineProperty(_this,"setupIframe",function(iframe){if(!iframe.getAttribute("content-set")){if(iframe.contentWindow&&iframe.contentWindow.document){iframe.setAttribute("content-set",true);iframe.contentWindow.document.open();iframe.contentWindow.document.write(_this.cyclomediaIndexHtml());iframe.contentWindow.document.close();_this.iframe=iframe}}else if(!iframe.getAttribute("callback-registered")){if(iframe.contentWindow&&iframe.contentWindow.registerCallbacks){iframe.setAttribute("callback-registered",true);iframe.contentWindow.registerCallbacks(_this.apiInitialized,_this.panoramaPositionChanged,_this.measurementChanged)}}else if(!iframe.getAttribute("init-called")){if(iframe.contentWindow&&iframe.contentWindow.StreetSmartApi){iframe.setAttribute("init-called",true);iframe.contentWindow.initApi()}}else{clearInterval(_this.iframePollIntervall)}});_defineProperty(_this,"apiInitialized",function(success){var message=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"";_this.setState({status:success?Status.INITIALIZED:Status.LOGIN,message:message,loginFailed:!success})});_defineProperty(_this,"panoramaPositionChanged",function(posData){if(_this.state.status!==Status.HAVEPOS){_this.setState({status:Status.HAVEPOS})}var scale=50;var angle=posData.hFov/2;var width=Math.sin(angle);var length=Math.sqrt(1-width*width);var size=scale/Math.sqrt(width*length);var coordinates=[[0,0],[size*width*2,0],[size*width,size*length]];var dimensions=[coordinates[1][0]+0.5,coordinates[2][1]+0.5];var canvas=document.createElement("canvas");canvas.width=dimensions[0];canvas.height=dimensions[1];var context=canvas.getContext("2d");context.fillStyle="rgba(255, 0, 0, 0.5)";context.strokeStyle="#FF0000";context.lineWidth=1;context.beginPath();context.moveTo(coordinates[0][0],coordinates[0][1]);coordinates.slice(1).forEach(function(coo){return context.lineTo(coo[0],coo[1])});context.closePath();context.fill();ResourceRegistry.addResource("cyclomedia-cone",context.canvas.toDataURL());var feature={geometry:{type:"Point",coordinates:posData.pos},crs:posData.crs,styleName:"image",styleOptions:{img:"cyclomedia-cone",rotation:posData.yaw,size:dimensions}};var layer={id:"cyclomedia-cone",role:LayerRole.MARKER};_this.props.addLayerFeatures(layer,[feature],true)});_defineProperty(_this,"measurementChanged",function(measurement){if(_this.props.displayMeasurements){if(measurement){var layer={id:"cyclomedia-measurements",role:LayerRole.MARKER,crs:measurement.crs.properties.name,styleOptions:{strokeColor:"red",strokeWidth:4,fillColor:[255,0,0,0.25],strokeDash:[]}};_this.props.addLayerFeatures(layer,measurement.features,true)}else{_this.props.removeLayer("cyclomedia-measurements")}}});_defineProperty(_this,"cyclomediaIndexHtml",function(){var supportedLang=["de","en-GB","en-US","fi","fr","nl","tr","pl"];var lang=LocaleUtils.lang();if(supportedLang.indexOf(lang)<0){lang=lang.slice(0,2);if(supportedLang.indexOf(lang)<0){lang="en-US"}}var loginOauth=!!_this.props.clientId&&!_this.state.loginFailed;return"\n <!DOCTYPE html>\n <html>\n <head>\n <script type=\"text/javascript\" src=\"https://unpkg.com/react@16.12.0/umd/react.production.min.js\"></script>\n <script type=\"text/javascript\" src=\"https://unpkg.com/react-dom@16.12.0/umd/react-dom.production.min.js\"></script>\n <script type=\"text/javascript\" src=\"https://streetsmart.cyclomedia.com/api/v".concat(_this.props.cyclomediaVersion,"/StreetSmartApi.js\"></script>\n <script type=\"text/javascript\">\n let apiInitialized = false;\n let initCallback = null;\n let posCallback = null;\n let measureCallback = null;\n\n function initApi() {\n StreetSmartApi.init({\n targetElement: document.getElementById(\"streetsmartApi\"),\n username: \"").concat(_this.state.username||undefined,"\",\n password: \"").concat(_this.state.password||undefined,"\",\n apiKey: \"").concat(_this.props.apikey,"\",\n clientId: \"").concat(_this.props.clientId,"\",\n loginOauth: ").concat(loginOauth,",\n loginRedirectUri: \"").concat(_this.props.loginRedirectUri,"\",\n logoutRedirectUri: \"").concat(_this.props.logoutRedirectUri,"\",\n srs: \"").concat(_this.props.projection,"\",\n locale: \"").concat(lang,"\",\n configurationUrl: 'https://atlas.cyclomedia.com/configuration',\n addressSettings: {\n locale: \"us\",\n database: \"Nokia\"\n }\n }).then(() => {\n apiInitialized = true;\n if (initCallback) {\n initCallback(true);\n }\n }, (e) => {\n apiInitialized = false;\n if (initCallback) {\n initCallback(false, e.message);\n }\n });\n }\n function openImage(posStr, crs) {\n if (!apiInitialized) {\n return;\n }\n StreetSmartApi.open(posStr, {\n viewerType: StreetSmartApi.ViewerType.PANORAMA,\n srs: crs,\n panoramaViewer: {\n closable: false,\n maximizable: true,\n replace: true,\n recordingsVisible: true,\n navbarVisible: true,\n timeTravelVisible: true,\n measureTypeButtonVisible: true,\n measureTypeButtonStart: true,\n measureTypeButtonToggle: true,\n },\n }).then((result) => {\n if (result && result[0]){\n window.panoramaViewer = result[0];\n window.panoramaViewer.on(StreetSmartApi.Events.panoramaViewer.IMAGE_CHANGE, changeView);\n window.panoramaViewer.on(StreetSmartApi.Events.panoramaViewer.VIEW_CHANGE, changeView);\n StreetSmartApi.on(StreetSmartApi.Events.measurement.MEASUREMENT_CHANGED, changeMeasurement);\n StreetSmartApi.on(StreetSmartApi.Events.measurement.MEASUREMENT_STOPPED, stopMeasurement);\n }\n }).catch((reason) => {\n console.log('Failed to create component(s) through API: ' + reason);\n });\n }\n function changeView() {\n if (posCallback) {\n const recording = window.panoramaViewer.getRecording();\n const orientation = window.panoramaViewer.getOrientation();\n const pos = recording.xyz;\n const posData = {\n pos: [pos[0], pos[1]],\n crs: recording.srs,\n yaw: orientation.yaw * Math.PI / 180,\n hFov: orientation.hFov * Math.PI / 180.0\n }\n posCallback(posData);\n }\n }\n function changeMeasurement(e) {\n measureCallback(e.detail.activeMeasurement);\n }\n function stopMeasurement() {\n measureCallback(null);\n }\n function registerCallbacks(_initCallback, _posCallback, _measureCallback) {\n initCallback = _initCallback;\n posCallback = _posCallback;\n measureCallback = _measureCallback;\n }\n </script>\n <style>\n html, body, #streetsmartApi {height: 100%;}\n </style>\n </head>\n <body style=\"margin: 0\">\n <div id=\"streetsmartApi\">\n </div>\n </body>\n </html>\n ")});_defineProperty(_this,"addRecordingsWFS",function(){var layer={id:"cyclomedia-recordings",type:"wfs",loader:function loader(vectorSource,extent,resolution,projection,success,failure){var bbox=CoordinatesUtils.reprojectBbox(extent,projection.getCode(),_this.props.mapCrs);var bboxstr=bbox.join(",");var reqUrl="https://atlasapi.cyclomedia.com/api/recording/wfs?service=WFS&version=1.1.0&request=GetFeature&typename=atlas:Recording&srsname=".concat(_this.props.mapCrs,"&bbox=").concat(bboxstr,"&maxFeatures=10000000");var xhr=new XMLHttpRequest;xhr.open("GET",reqUrl);xhr.setRequestHeader("Authorization","Basic "+btoa(_this.state.username+":"+_this.state.password));var onError=function onError(){vectorSource.removeLoadedExtent(extent);failure()};xhr.onerror=onError;xhr.onload=function(){if(xhr.status===200){var features=vectorSource.getFormat().readFeatures(xhr.responseText,{dataProjection:_this.props.mapCrs,featureProjection:projection.getCode()});vectorSource.addFeatures(features);success(features)}else{onError()}};xhr.send()},name:"atlas:Recording",version:"1.1.0",projection:_this.props.mapCrs,formats:["text/xml; subtype=gml/3.1.1"],invertAxisOrientation:true,role:LayerRole.SELECTION,color:"#6666FF",visibility:_this.props.mapScale<=_this.props.maxMapScale};_this.props.addLayer(layer)});_defineProperty(_this,"queryPoint",function(prevProps){if(_this.props.click===prevProps.click){return null}var cmFeature=_this.props.click.features.find(function(feature){return feature.layerId==="cyclomedia-recordings"});return cmFeature?cmFeature.geometry.coordinates:null});_this.iframe=null;_this.iframePollIntervall=null;if(props.credentialUserInfoFields){_this.state.username=props.userInfos[props.credentialUserInfoFields.username];_this.state.password=props.userInfos[props.credentialUserInfoFields.password]}return _this}_inherits(Cyclomedia,_React$Component);return _createClass(Cyclomedia,[{key:"componentDidUpdate",value:function componentDidUpdate(prevProps,prevState){if(!prevProps.active&&this.props.active){this.setState({status:this.props.clientId?Status.INITIALIZING:Status.LOGIN,loginFailed:false})}else if(prevProps.active&&!this.props.active||prevProps.theme&&!this.props.theme){this.onClose()}// Load WFS when loading
if(this.state.status===Status.INITIALIZING&&prevState.status<Status.INITIALIZING){this.addRecordingsWFS()}// Handle map click events
if((this.state.status===Status.INITIALIZED||this.state.status===Status.HAVEPOS)&&this.iframe){var clickPoint=this.queryPoint(prevProps);if(clickPoint){var posStr=clickPoint[0]+","+clickPoint[1];this.iframe.contentWindow.openImage(posStr,this.props.mapCrs);if(this.state.status!==Status.LOADPOS){this.setState({status:Status.LOADPOS});this.props.removeLayer("cyclomedia-cone");this.props.removeLayer("cyclomedia-measurements");ResourceRegistry.removeResource("cyclomedia-cone")}}}if(this.props.active&&this.props.mapScale!==prevProps.mapScale){this.props.changeLayerProperty("cyclomedia-recordings","visibility",this.props.mapScale<=this.props.maxMapScale)}if(this.state.status===Status.LOGIN&&prevState.status>Status.LOGIN){this.props.removeLayer("cyclomedia-recordings");this.props.removeLayer("cyclomedia-cone");this.props.removeLayer("cyclomedia-measurements");ResourceRegistry.removeResource("cyclomedia-cone")}}},{key:"render",value:function render(){var _this2=this;if(!this.props.active){return null}var overlay=null;if(this.state.status===Status.LOGIN){overlay=/*#__PURE__*/React.createElement("div",{className:"cyclomedia-body-overlay"},/*#__PURE__*/React.createElement("div",{className:"cyclomedia-login"},/*#__PURE__*/React.createElement("table",null,/*#__PURE__*/React.createElement("tbody",null,/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("td",null,"Username:"),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("input",{onChange:function onChange(ev){return _this2.setState({username:ev.target.value})},type:"text",value:this.state.username}))),/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("td",null,"Password:"),/*#__PURE__*/React.createElement("td",null,/*#__PURE__*/React.createElement("input",{onChange:function onChange(ev){return _this2.setState({password:ev.target.value})},type:"password",value:this.state.password}))),/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("td",{colSpan:"2"},/*#__PURE__*/React.createElement("button",{className:"button",disabled:!this.state.username,onClick:function onClick(){return _this2.setState({status:Status.INITIALIZING})},type:"button"},LocaleUtils.tr("cyclomedia.login")))),/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("td",{className:"cyclomedia-login-message",colSpan:"2"},this.state.message))))))}else if(this.state.status===Status.INITIALIZING){overlay=/*#__PURE__*/React.createElement("div",{className:"cyclomedia-body-overlay"},/*#__PURE__*/React.createElement(Spinner,null),/*#__PURE__*/React.createElement("span",null,LocaleUtils.tr("cyclomedia.initializing")))}else if(this.state.status===Status.ERROR){overlay=/*#__PURE__*/React.createElement("div",{className:"cyclomedia-body-overlay"},/*#__PURE__*/React.createElement("span",null,LocaleUtils.tr("cyclomedia.loaderror")))}else if(this.state.status===Status.INITIALIZED){overlay=/*#__PURE__*/React.createElement("div",{className:"cyclomedia-body-overlay"},/*#__PURE__*/React.createElement("span",null,LocaleUtils.tr("cyclomedia.clickonmap")))}else if(this.state.status===Status.LOADPOS){overlay=/*#__PURE__*/React.createElement("div",{className:"cyclomedia-body-overlay"},/*#__PURE__*/React.createElement(Spinner,null),/*#__PURE__*/React.createElement("span",null,LocaleUtils.tr("cyclomedia.loading")))}return/*#__PURE__*/React.createElement(ResizeableWindow,{dockable:this.props.geometry.side,icon:"cyclomedia",initialHeight:this.props.geometry.initialHeight,initialWidth:this.props.geometry.initialWidth,initialX:this.props.geometry.initialX,initialY:this.props.geometry.initialY,initiallyDocked:this.props.geometry.initiallyDocked,onClose:this.onClose,splitScreenWhenDocked:true,title:LocaleUtils.tr("cyclomedia.title"),usePortal:false},/*#__PURE__*/React.createElement("div",{className:"cyclomedia-body",role:"body"},this.props.mapScale>this.props.maxMapScale&&this.state.status>Status.LOGIN?/*#__PURE__*/React.createElement("div",{className:"cyclomedia-scale-hint"},LocaleUtils.tr("cyclomedia.scalehint",this.props.maxMapScale)):null,this.state.status>Status.LOGIN?/*#__PURE__*/React.createElement("iframe",{className:"cyclomedia-frame",onLoad:function onLoad(ev){return _this2.setupIframe(ev.target)},ref:function ref(el){return _this2.setIframeRef(el)}}):null,overlay))}}])}(React.Component);_defineProperty(Cyclomedia,"propTypes",{active:PropTypes.bool,addLayer:PropTypes.func,addLayerFeatures:PropTypes.func,/** The Cyclomedia API key */apikey:PropTypes.string,changeLayerProperty:PropTypes.func,click:PropTypes.object,/** OAuth client ID. */clientId:PropTypes.string,/** Fields from user_infos which contain username and password which will be pre-inserted into the login form. */credentialUserInfoFields:PropTypes.shape({username:PropTypes.string,password:PropTypes.string}),/** The cyclomedia version. */cyclomediaVersion:PropTypes.string,/** Whether to display Cyclomedia measurement geometries on the map. */displayMeasurements:PropTypes.bool,/** Default window geometry with size, position and docking status. Positive position values (including '0') are related to top (InitialY) and left (InitialX), negative values (including '-0') to bottom (InitialY) and right (InitialX). */geometry:PropTypes.shape({initialWidth:PropTypes.number,initialHeight:PropTypes.number,initialX:PropTypes.number,initialY:PropTypes.number,initiallyDocked:PropTypes.bool,side:PropTypes.string}),/** The relative path to the redirect login handling of oauth. */loginRedirectUri:PropTypes.string,/** The relative path to the redirect logout handling of oauth. */logoutRedirectUri:PropTypes.string,mapCrs:PropTypes.string,mapScale:PropTypes.number,/** The maximum map scale above which the recordings WFS won't be displayed. */maxMapScale:PropTypes.number,/** The projection to use for Cyclomedia. */projection:PropTypes.string,removeLayer:PropTypes.func,setCurrentTask:PropTypes.func,theme:PropTypes.object,userInfos:PropTypes.object});_defineProperty(Cyclomedia,"defaultProps",{cyclomediaVersion:"24.1",displayMeasurements:true,geometry:{initialWidth:480,initialHeight:640,initialX:0,initialY:0,initiallyDocked:false,side:"left"},maxMapScale:5000,projection:"EPSG:3857"});export default connect(function(state){return{active:state.task.id==="Cyclomedia",click:state.map.click,mapCrs:state.map.projection,mapScale:MapUtils.computeForZoom(state.map.scales,state.map.zoom),theme:state.theme.current,userInfos:state.localConfig.user_infos}},{addLayer:addLayer,addLayerFeatures:addLayerFeatures,changeLayerProperty:changeLayerProperty,removeLayer:removeLayer,setCurrentTask:setCurrentTask})(Cyclomedia);