Skip to content

Commit

Permalink
Merge pull request #4919 from camptocamp/GSGMF-993
Browse files Browse the repository at this point in the history
Add queryableLayers support in the API
  • Loading branch information
fredj committed Jun 7, 2019
2 parents 3ff2015 + 7316024 commit 6ed14f0
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 28 deletions.
2 changes: 1 addition & 1 deletion api/dist/apihelp/apihelp.html
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ <h2>A map with some additional controls</h2>
div: 'map5',
zoom: 3,
center: [544500, 210100],
layers: ['bank'],
layers: ['osm_open'],
addLayerSwitcher: true,
addMiniMap: true,
miniMapExpanded: true,
Expand Down
77 changes: 61 additions & 16 deletions api/src/Map.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,16 @@ import {get as getProjection} from 'ol/proj.js';

import constants from './constants.js';

import {getFeaturesFromLayer} from './Querent.js';
import {getFeaturesFromIds, getFeaturesFromCoordinates} from './Querent.js';
import * as themes from './Themes.js';


/**
* @type {Array<string>}
*/
const EXCLUDE_PROPERTIES = ['geom', 'geometry', 'boundedBy'];


/**
* @private
* @hidden
Expand Down Expand Up @@ -163,6 +169,26 @@ class Map {
this.selectObject(selected.getId());
}
});


this.map_.on('singleclick', (event) => {
const resolution = this.map_.getView().getResolution();
const visibleLayers = this.map_.getLayers().getArray().filter(layer => layer.getVisible());
const visibleLayersName = visibleLayers.map(layer => layer.get('config.name'));

this.clearSelection();

for (const layer of constants.queryableLayers) {
if (visibleLayersName.includes(layer)) {
getFeaturesFromCoordinates(layer, event.coordinate, resolution).then((feature) => {
if (feature) {
this.vectorSource_.addFeature(feature);
this.selectObject(feature.getId(), true);
}
});
}
}
});
}

/**
Expand All @@ -174,11 +200,8 @@ class Map {
overlayContainer.className = 'ol-popup';
const overlayCloser = document.createElement('div');
overlayCloser.className = 'ol-popup-closer';
overlayCloser.addEventListener('click', (event) => {
// clear the selected features
this.selectInteraction_.getFeatures().clear();
// hide the overlay
this.overlay_.setPosition(undefined);
overlayCloser.addEventListener('click', () => {
this.clearSelection();
return false;
});
const overlayContent = document.createElement('div');
Expand Down Expand Up @@ -223,11 +246,10 @@ class Map {
/**
* @param {string} layer Name of the layer to fetch the features from
* @param {Array.<string>} ids List of ids
* @param {boolean} [highlight=false] Whether to add the features on
* the map or not.
* @param {boolean} [highlight=false] Whether to add the features on the map or not.
*/
recenterOnObjects(layer, ids, highlight = false) {
getFeaturesFromLayer(layer, ids)
getFeaturesFromIds(layer, ids)
.then((features) => {
if (!features.length) {
console.error('Could not recenter: no objects were found.');
Expand Down Expand Up @@ -308,28 +330,51 @@ class Map {
}

/**
* @param {string} id Identifier.
* @param {string|number} id Identifier.
* @param {boolean} table Display all properties in a table
*/
selectObject(id) {
selectObject(id, table = false) {
const feature = this.vectorSource_.getFeatureById(id);
if (feature) {
const coordinates = /** @type {import('ol/geom/Point.js').default} */(
feature.getGeometry()
).getCoordinates();
const properties = feature.getProperties();
let contentHTML = '';
if (table) {
contentHTML += '<table><tbody>';
for (const key in properties) {
if (!EXCLUDE_PROPERTIES.includes(key)) {
contentHTML += '<tr>';
contentHTML += `<th>${key}</th>`;
contentHTML += `<td>${properties[key]}</td>`;
contentHTML += '</tr>';
}
}
contentHTML += '</tbody></table>';
} else {
contentHTML += `<div><b>${properties.title}</b></div>`;
contentHTML += `<p>${properties.description}</p>`;
}
const content = this.overlay_.getElement().querySelector('.ol-popup-content');
content.innerHTML = '';
content.innerHTML += `<div><b>${properties.title}</b></div>`;
content.innerHTML += `<p>${properties.description}</p>`;
content.innerHTML = contentHTML;
this.overlay_.setPosition(coordinates);

this.view_.setCenter(coordinates);
}
}

/**
*
*/
clearSelection() {
// clear the selected features
this.selectInteraction_.getFeatures().clear();
this.vectorSource_.clear();
// hide the overlay
this.overlay_.setPosition(undefined);
}
}


/**
* @param {Array.<string>} keys Keys.
* @param {Array.<*>} values Values.
Expand Down
83 changes: 76 additions & 7 deletions api/src/Querent.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@ import {getOverlayDefs} from './Themes.js';
import {appendParams as olUriAppendParams} from 'ol/uri.js';
import olFormatGML2 from 'ol/format/GML2.js';
import olFormatWFS from 'ol/format/WFS.js';
import {buffer, createOrUpdateFromCoordinate} from 'ol/extent.js';


/**
* Click tolerance in pixel
* @type {number}
*/
const TOLERANCE = 10;


/**
* @param {import('./Themes.js').overlayDefinition} def Overlay definition.
* @return {boolean} Is the overlay queryable.
*/
function querable(def) {
return def.layer.type === 'WMS' && !!def.ogcServer.wfsSupport && !!def.ogcServer.urlWfs;
}


/**
* Issues a simple WFS GetFeature request for a single layer to fetch
Expand All @@ -18,7 +36,7 @@ import olFormatWFS from 'ol/format/WFS.js';
* @return {!Promise<Array<import('ol/Feature.js').default>>} Promise.
* @hidden
*/
export function getFeaturesFromLayer(layer, ids) {
export function getFeaturesFromIds(layer, ids) {
return new Promise((resolve, reject) => {
getOverlayDefs().then((overlayDefs) => {

Expand All @@ -31,12 +49,7 @@ export function getFeaturesFromLayer(layer, ids) {
return;
}

if (
!overlayDef.ogcServer ||
!overlayDef.ogcServer.wfsSupport ||
!overlayDef.ogcServer.urlWfs ||
overlayDef.layer.type !== 'WMS'
) {
if (!querable(overlayDef)) {
reject(`Layer "${layer}" does not support WFS.`);
return;
}
Expand Down Expand Up @@ -70,3 +83,59 @@ export function getFeaturesFromLayer(layer, ids) {
});
});
}


/**
* @param {!string} layer Name of the layer to query
* @param {Array.<number>} coordinate Coordinate.
* @param {number} resolution Resolution
*
* @return {!Promise<import('ol/Feature.js').default>} Promise.
* @hidden
*/
export function getFeaturesFromCoordinates(layer, coordinate, resolution) {
return new Promise((resolve, reject) => {
getOverlayDefs().then((overlayDefs) => {

const overlayDef = overlayDefs.get(layer);

if (!overlayDef) {
reject(`Layer "${layer}" was not found in themes.`);
return;
}

if (!querable(overlayDef)) {
reject(`Layer "${layer}" does not support WFS.`);
return;
}

const bbox = buffer(createOrUpdateFromCoordinate(coordinate), TOLERANCE * resolution);

const params = {
'BBOX': bbox.join(','),
'MAXFEATURES': 1,
'REQUEST': 'GetFeature',
'SERVICE': 'WFS',
'TYPENAME': layer,
'VERSION': '1.0.0'
};
const url = olUriAppendParams(overlayDef.ogcServer.urlWfs, params);

let feature;
fetch(url)
.then(response => response.text().then((responseText) => {
const wfsFormat = new olFormatWFS({
featureNS: overlayDef.ogcServer.namespace,
gmlFormat: new olFormatGML2()
});
feature = wfsFormat.readFeature(responseText);
}))
.catch((response) => {
console.error(`WFS GetFeature request failed, response: ${response}`);
})
.then(() => {
resolve(feature);
});
});
});
}
10 changes: 6 additions & 4 deletions api/src/Themes.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ export function getBackgroundLayers() {
const layerWMTS = /** @type {import('gmf/themes.js').GmfLayerWMTS} */(config);
promises.push(
createWMTSLayer(layerWMTS).then((layer) => {
layer.set('config.layer', layerWMTS.layer);
layer.set('config.name', layerWMTS.name);
return layer;
})
Expand All @@ -52,7 +51,6 @@ export function getBackgroundLayers() {
const ogcServer = themes.ogcServers[config.ogcServer];
promises.push(
createWMSLayer(layerWMS, ogcServer).then((layer) => {
layer.set('config.layer', layerWMS.layers);
layer.set('config.name', layerWMS.name);
return layer;
})
Expand Down Expand Up @@ -189,14 +187,17 @@ export function createWMSLayer(config, ogcServer) {
const layer = new ImageLayer({
source: new ImageWMS({
url: ogcServer.url,
projection: undefined, // should be removed in next OL version
projection: undefined, // FIXME: should be removed in next OL version
params: {
'LAYERS': config.layers
},
serverType: ogcServer.type
})
}),
minResolution: config.minResolutionHint,
maxResolution: config.maxResolutionHint
});
layer.set('title', config.name);
layer.set('config.name', config.name);
return Promise.resolve(layer);
}

Expand All @@ -219,6 +220,7 @@ export function createWMTSLayer(config) {
source: source
});
layer.set('title', config.name);
layer.set('config.name', config.name);
return layer;
});
}
Expand Down
5 changes: 5 additions & 0 deletions api/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ export default {
* (WMTS) or a comma-separated list of layer names (WMS).
*/
backgroundLayer: 'orthophoto',

/**
* The list of layers (names) declared as queryable.
*/
queryableLayers: ['osm_open', 'many_attributes']
};

0 comments on commit 6ed14f0

Please sign in to comment.