Skip to content

Commit

Permalink
8576 No feedback loading while query 3D Tiles layers (Identify) (geos…
Browse files Browse the repository at this point in the history
…olutions-it#8602) (geosolutions-it#8631)

tested locally, successfully on 2022.01.xx
  • Loading branch information
allyoucanmap authored Sep 29, 2022
1 parent 8571576 commit 0f18d6a
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 88 deletions.
7 changes: 7 additions & 0 deletions web/client/actions/maplayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

export const UPDATE_MAP_LAYOUT = 'MAP_LAYOUT:UPDATE_MAP_LAYOUT';
export const FORCE_UPDATE_MAP_LAYOUT = 'MAP_LAYOUT:FORCE_UPDATE_MAP_LAYOUT';

/**
* updateMapLayout action, type `UPDATE_MAP_LAYOUT`
Expand All @@ -21,6 +22,12 @@ export function updateMapLayout(layout) {
};
}

export function forceUpdateMapLayout() {
return {
type: FORCE_UPDATE_MAP_LAYOUT
};
}

/**
* Actions for map layout.
* @name actions.mapLayout
Expand Down
33 changes: 13 additions & 20 deletions web/client/components/map/cesium/Map.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,9 @@ class CesiumMap extends React.Component {
x: x,
y: y
},
height: this.props.mapOptions && this.props.mapOptions.terrainProvider ? cartographic.height : undefined,
height: (this.props.mapOptions && this.props.mapOptions.terrainProvider) || intersectedFeatures.length > 0
? cartographic.height
: undefined,
cartographic,
latlng: {
lat: latitude,
Expand Down Expand Up @@ -293,25 +295,16 @@ class CesiumMap extends React.Component {
};

getIntersectedFeatures = (map, position) => {
const features = map.scene.drillPick(position);
if (features) {
const groupIntersectedFeatures = features.reduce((acc, feature) => {
if (feature instanceof Cesium.Cesium3DTileFeature && feature?.tileset?.msId) {
const msId = feature.tileset.msId;
// 3d tile feature does not contain a geometry in the Cesium3DTileFeature class
// it has content but refers to the whole tile model
const propertyNames = feature.getPropertyNames();
const properties = Object.fromEntries(propertyNames.map(key => [key, feature.getProperty(key)]));
return {
...acc,
[msId]: acc[msId]
? [...acc[msId], { type: 'Feature', properties, geometry: null }]
: [{ type: 'Feature', properties, geometry: null }]
};
}
return acc;
}, []);
return Object.keys(groupIntersectedFeatures).map(id => ({ id, features: groupIntersectedFeatures[id] }));
// we can use pick so the only first intersect feature will be returned
// this is more intuitive for uses such as get feature info
const feature = map.scene.pick(position);
if (feature instanceof Cesium.Cesium3DTileFeature && feature?.tileset?.msId) {
const msId = feature.tileset.msId;
// 3d tile feature does not contain a geometry in the Cesium3DTileFeature class
// it has content but refers to the whole tile model
const propertyNames = feature.getPropertyNames();
const properties = Object.fromEntries(propertyNames.map(key => [key, feature.getProperty(key)]));
return [{ id: msId, features: [{ type: 'Feature', properties, geometry: null }] }];
}
return [];
}
Expand Down
30 changes: 21 additions & 9 deletions web/client/containers/MapViewer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,33 @@ import React from 'react';

import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import assign from 'object-assign';
import url from 'url';
import isEqual from 'lodash/isEqual';
const urlQuery = url.parse(window.location.href, true).query;

import ConfigUtils from '../utils/ConfigUtils';
import { getMonitoredState } from '../utils/PluginsUtils';
import { createShallowSelectorCreator } from '../utils/ReselectUtils';

const PluginsContainer = connect((state) => ({
statePluginsConfig: state.plugins,
mode: urlQuery.mode || state.mode || (state.browser && state.browser.mobile ? 'mobile' : 'desktop'),
pluginsState: assign({}, state && state.controls, state && state.layers && state.layers.settings && {
layerSettings: state.layers.settings
}),
monitoredState: getMonitoredState(state, ConfigUtils.getConfigProp('monitorState'))
}))(require('../components/plugins/PluginsContainer').default);
const PluginsContainer = connect(
createShallowSelectorCreator(isEqual)(
state => state.plugins,
state => state.mode,
state => state?.browser?.mobile,
state => state.controls,
state => state?.layers?.settings,
state => getMonitoredState(state, ConfigUtils.getConfigProp('monitorState')),
(statePluginsConfig, stateMode, mobile, controls, layerSettings, monitoredState) => ({
statePluginsConfig,
mode: urlQuery.mode || stateMode || (mobile ? 'mobile' : 'desktop'),
pluginsState: {
...controls,
...(layerSettings && { layerSettings })
},
monitoredState
})
)
)(require('../components/plugins/PluginsContainer').default);

class MapViewer extends React.Component {
static propTypes = {
Expand Down
17 changes: 12 additions & 5 deletions web/client/epics/__tests__/identify-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import { setControlProperties } from '../../actions/controls';
import { BROWSE_DATA } from '../../actions/layers';
import { configureMap } from '../../actions/config';
import { changeMapType } from './../../actions/maptype';
import { FORCE_UPDATE_MAP_LAYOUT } from '../../actions/maplayout';

const TEST_MAP_STATE = {
present: {
Expand Down Expand Up @@ -173,11 +174,11 @@ describe('identify Epics', () => {
}
};
const sentActions = [featureInfoClick({ latlng: { lat: 36.95, lng: -79.84 } })];
const NUM_ACTIONS = 5;
const NUM_ACTIONS = 6;
testEpic(getFeatureInfoOnFeatureInfoClick, NUM_ACTIONS, sentActions, (actions) => {
try {
expect(actions.length).toBe(5);
const [a0, a1, a2, a3, a4] = actions;
expect(actions.length).toBe(6);
const [a0, a1, a2, a3, a4, a5] = actions;
expect(a0).toExist();
expect(a0.type).toBe(PURGE_MAPINFO_RESULTS);
expect(a1).toExist();
Expand All @@ -194,8 +195,12 @@ describe('identify Epics', () => {
expect(a3.requestParams).toExist();
expect(a3.reqId).toExist();
expect(a3.layerMetadata.title).toBe(state.layers.flat[a3.requestParams.id === "TEST" ? 0 : 1].title);

expect(a4).toExist();
expect(a4.layerMetadata.title).toBe(state.layers.flat[a4.requestParams.id === "TEST" ? 0 : 1].title);
expect(a4.type).toBe(FORCE_UPDATE_MAP_LAYOUT);

expect(a5).toExist();
expect(a5.layerMetadata.title).toBe(state.layers.flat[a5.requestParams.id === "TEST" ? 0 : 1].title);
done();
} catch (ex) {
done(ex);
Expand Down Expand Up @@ -580,7 +585,7 @@ describe('identify Epics', () => {
}, state);
});
it('getFeatureInfoOnFeatureInfoClick with enableInfoForSelectedLayers set to false', (done) => {
const NUM_ACTIONS = 5;
const NUM_ACTIONS = 6;
const state = {
map: TEST_MAP_STATE,
mapInfo: {
Expand Down Expand Up @@ -624,6 +629,8 @@ describe('identify Epics', () => {
expect(action.reqId).toBeTruthy();
expect([state.layers.flat[0].title, state.layers.flat[1].title].includes(action.layerMetadata.title)).toBeTruthy();
break;
case FORCE_UPDATE_MAP_LAYOUT:
break;
default:
expect(true).toBe(false);

Expand Down
25 changes: 23 additions & 2 deletions web/client/epics/identify.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { closeAnnotations } from '../actions/annotations';
import { MAP_CONFIG_LOADED } from '../actions/config';
import {addPopup, cleanPopups, removePopup, REMOVE_MAP_POPUP} from '../actions/mapPopups';
import { cancelSelectedItem } from '../actions/search';
import { forceUpdateMapLayout } from '../actions/maplayout';
import {
stopGetFeatureInfoSelector, identifyOptionsSelector,
clickPointSelector, clickLayerSelector,
Expand Down Expand Up @@ -96,6 +97,9 @@ export const getFeatureInfoOnFeatureInfoClick = (action$, { getState = () => { }
"filter",
"propertyName"
];

let firstResponseReturned = false;

const out$ = Rx.Observable.from((queryableLayers.filter(l => {
// filtering a subset of layers
return filterNameList.length ? (filterNameList.filter(name => name.indexOf(l.name) !== -1).length > 0) : true;
Expand All @@ -120,12 +124,26 @@ export const getFeatureInfoOnFeatureInfoClick = (action$, { getState = () => { }
const reqId = uuid.v1();
const param = { ...appParams, ...requestParams };
return getFeatureInfo(basePath, param, layer, {attachJSON, itemId})
// this 0 delay is needed for vector/3dtiles layer because makes the response async and give time to the GUI to render
// these type of layers don't perform requests to the server because the values are taken from the client map so the response were applied synchronously
// this delay allows the panel to open and show the spinner for the first one
// this delay mitigates the freezing of the app when there are a great amount of queried layers at the same time
.delay(0)
.map((response) =>
response.data.exceptions
? exceptionsFeatureInfo(reqId, response.data.exceptions, requestParams, lMetaData)
: loadFeatureInfo(reqId, response.data, requestParams, { ...lMetaData, features: response.features, featuresCrs: response.featuresCrs }, layer)
)
.catch((e) => Rx.Observable.of(errorFeatureInfo(reqId, e.data || e.statusText || e.status, requestParams, lMetaData)))
.concat(Rx.Observable.defer(() => {
// update the layout only after the initial response
// we don't need to trigger this for each query layer
if (!firstResponseReturned) {
firstResponseReturned = true;
return Rx.Observable.of(forceUpdateMapLayout());
}
return Rx.Observable.empty();
}))
.startWith(newMapInfoRequest(reqId, param));
}
return Rx.Observable.of(getVectorInfo(layer, request, metadata, queryableLayers));
Expand All @@ -147,10 +165,13 @@ export const handleMapInfoMarker = (action$, {getState}) =>
? hideMapinfoMarker()
: showMapinfoMarker()
);
export const closeFeatureGridFromIdentifyEpic = (action$) =>
export const closeFeatureGridFromIdentifyEpic = (action$, store) =>
action$.ofType(LOAD_FEATURE_INFO, GET_VECTOR_INFO)
.switchMap(() => {
return Rx.Observable.of(closeFeatureGrid());
if (isFeatureGridOpen(store.getState())) {
return Rx.Observable.of(closeFeatureGrid());
}
return Rx.Observable.empty();
});
/**
* Check if something is editing in feature info.
Expand Down
12 changes: 4 additions & 8 deletions web/client/epics/maplayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@
*/
import Rx from 'rxjs';

import { updateMapLayout } from '../actions/maplayout';
import { TOGGLE_CONTROL, SET_CONTROL_PROPERTY, SET_CONTROL_PROPERTIES } from '../actions/controls';
import {updateMapLayout, FORCE_UPDATE_MAP_LAYOUT} from '../actions/maplayout';
import {TOGGLE_CONTROL, SET_CONTROL_PROPERTY, SET_CONTROL_PROPERTIES} from '../actions/controls';
import { MAP_CONFIG_LOADED } from '../actions/config';
import { SIZE_CHANGE, CLOSE_FEATURE_GRID, OPEN_FEATURE_GRID } from '../actions/featuregrid';

import {
CLOSE_IDENTIFY,
ERROR_FEATURE_INFO,
TOGGLE_MAPINFO_STATE,
LOAD_FEATURE_INFO,
EXCEPTIONS_FEATURE_INFO,
NO_QUERYABLE_LAYERS
} from '../actions/mapInfo';

Expand Down Expand Up @@ -55,14 +52,13 @@ export const updateMapLayoutEpic = (action$, store) =>
CLOSE_IDENTIFY,
NO_QUERYABLE_LAYERS,
TOGGLE_MAPINFO_STATE,
LOAD_FEATURE_INFO,
EXCEPTIONS_FEATURE_INFO,
TOGGLE_CONTROL,
SET_CONTROL_PROPERTY,
SET_CONTROL_PROPERTIES,
SHOW_SETTINGS,
HIDE_SETTINGS,
ERROR_FEATURE_INFO)
FORCE_UPDATE_MAP_LAYOUT
)
.switchMap(() => {
const state = store.getState();

Expand Down
46 changes: 23 additions & 23 deletions web/client/plugins/TOC.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import PropTypes from 'prop-types';

import React from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { compose, branch, withPropsOnChange } from 'recompose';
import { Glyphicon } from 'react-bootstrap';

Expand Down Expand Up @@ -66,6 +65,8 @@ import { createWidget } from '../actions/widgets';
import { getMetadataRecordById } from '../actions/catalog';
import { activeSelector } from '../selectors/catalog';
import { isCesium } from '../selectors/maptype';
import { createShallowSelectorCreator } from '../utils/ReselectUtils';
import isEqual from 'lodash/isEqual';

const addFilteredAttributesGroups = (nodes, filters) => {
return nodes.reduce((newNodes, currentNode) => {
Expand All @@ -91,28 +92,27 @@ const filterLayersByTitle = (layer, filterText, currentLocale) => {
return title.toLowerCase().indexOf(filterText.toLowerCase()) !== -1;
};

const tocSelector = createSelector(
[
(state) => state.controls && state.controls.toolbar && state.controls.toolbar.active === 'toc',
groupsSelector,
layerSettingSelector,
layerSwipeSettingsSelector,
layerMetadataSelector,
wfsDownloadSelector,
mapSelector,
currentLocaleSelector,
currentLocaleLanguageSelector,
selectedNodesSelector,
layerFilterSelector,
layersSelector,
mapNameSelector,
activeSelector,
widgetBuilderAvailable,
generalInfoFormatSelector,
isCesium,
userSelector,
isLocalizedLayerStylesEnabledSelector
], (enabled, groups, settings, swipeSettings, layerMetadata, layerdownload, map, currentLocale, currentLocaleLanguage, selectedNodes, filterText, layers, mapName, catalogActive, activateWidgetTool, generalInfoFormat, isCesiumActive, user, isLocalizedLayerStylesEnabled) => ({
const tocSelector = createShallowSelectorCreator(isEqual)(
(state) => state.controls && state.controls.toolbar && state.controls.toolbar.active === 'toc',
groupsSelector,
layerSettingSelector,
layerSwipeSettingsSelector,
layerMetadataSelector,
wfsDownloadSelector,
mapSelector,
currentLocaleSelector,
currentLocaleLanguageSelector,
selectedNodesSelector,
layerFilterSelector,
layersSelector,
mapNameSelector,
activeSelector,
widgetBuilderAvailable,
generalInfoFormatSelector,
isCesium,
userSelector,
isLocalizedLayerStylesEnabledSelector,
(enabled, groups, settings, swipeSettings, layerMetadata, layerdownload, map, currentLocale, currentLocaleLanguage, selectedNodes, filterText, layers, mapName, catalogActive, activateWidgetTool, generalInfoFormat, isCesiumActive, user, isLocalizedLayerStylesEnabled) => ({
enabled,
groups,
settings,
Expand Down
47 changes: 31 additions & 16 deletions web/client/plugins/map/selector.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
import { createStructuredSelector } from 'reselect';

import { mapSelector, projectionDefsSelector, isMouseMoveCoordinatesActiveSelector } from '../../selectors/map';
import { mapTypeSelector, isOpenlayers } from '../../selectors/maptype';
import { layerSelectorWithMarkers } from '../../selectors/layers';
import { highlighedFeatures } from '../../selectors/highlight';
import { securityTokenSelector } from '../../selectors/security';
import { currentLocaleLanguageSelector } from '../../selectors/locale';
import { isLocalizedLayerStylesEnabledSelector, localizedLayerStylesNameSelector } from '../../selectors/localizedLayerStyles';
import { createShallowSelectorCreator } from '../../utils/ReselectUtils';
import isEqual from 'lodash/isEqual';

/**
* Map state to props selector for Map plugin
*/
export default createStructuredSelector({
projectionDefs: projectionDefsSelector,
map: mapSelector,
mapType: mapTypeSelector,
layers: layerSelectorWithMarkers,
features: highlighedFeatures,
loadingError: state => state.mapInitialConfig && state.mapInitialConfig.loadingError && state.mapInitialConfig.loadingError.data,
securityToken: securityTokenSelector,
elevationEnabled: isMouseMoveCoordinatesActiveSelector,
shouldLoadFont: isOpenlayers,
isLocalizedLayerStylesEnabled: isLocalizedLayerStylesEnabledSelector,
localizedLayerStylesName: localizedLayerStylesNameSelector,
currentLocaleLanguage: currentLocaleLanguageSelector
});

export default createShallowSelectorCreator(isEqual)(
projectionDefsSelector,
mapSelector,
mapTypeSelector,
layerSelectorWithMarkers,
highlighedFeatures,
state => state.mapInitialConfig && state.mapInitialConfig.loadingError && state.mapInitialConfig.loadingError.data,
securityTokenSelector,
isMouseMoveCoordinatesActiveSelector,
isOpenlayers,
isLocalizedLayerStylesEnabledSelector,
localizedLayerStylesNameSelector,
currentLocaleLanguageSelector,
(projectionDefs, map, mapType, layers, features, loadingError, securityToken, elevationEnabled, shouldLoadFont, isLocalizedLayerStylesEnabled, localizedLayerStylesName, currentLocaleLanguage) => ({
projectionDefs,
map,
mapType,
layers,
features,
loadingError,
securityToken,
elevationEnabled,
shouldLoadFont,
isLocalizedLayerStylesEnabled,
localizedLayerStylesName,
currentLocaleLanguage
})
);
Loading

0 comments on commit 0f18d6a

Please sign in to comment.