Skip to content

Commit

Permalink
Merge pull request #6445 from camptocamp/click_tolerence_per_layer_gs…
Browse files Browse the repository at this point in the history
…gmf-1294

Click tolerence per layer
  • Loading branch information
ger-benjamin committed Nov 25, 2020
2 parents 28bd8dc + 074147a commit 2b0c915
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 37 deletions.
20 changes: 11 additions & 9 deletions contribs/gmf/src/datasource/Manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,7 @@ export class DatasourceManager {
const copyable = meta.copyable;
const identifierAttribute = meta.identifierAttributeField;
const name = gmfLayer.name;
const queryIconPosition = meta.queryIconPosition;
const timeAttributeName = meta.timeAttribute;
const visible = meta.isChecked === true;
const ogcAttributes = ogcServer ? ogcServer.attributes : null;
Expand All @@ -656,6 +657,7 @@ export class DatasourceManager {
name,
ogcType,
ogcAttributes,
queryIconPosition,
snappable,
timeAttributeName,
visible,
Expand All @@ -673,18 +675,9 @@ export class DatasourceManager {
if (ogcImageType) {
options.ogcImageType = ogcImageType;
}
if (wmsLayers) {
options.wmsLayers = wmsLayers;
}
if (wfsLayers) {
options.wfsLayers = wfsLayers;
}
if (ogcServerType) {
options.ogcServerType = ogcServerType;
}
if (wfsFeatureNS) {
options.wfsFeatureNS = wfsFeatureNS;
}
if (snappingTolerance) {
options.snappingTolerance = snappingTolerance;
}
Expand All @@ -703,12 +696,21 @@ export class DatasourceManager {
if (timeUpperValue) {
options.timeUpperValue = timeUpperValue;
}
if (wfsFeatureNS) {
options.wfsFeatureNS = wfsFeatureNS;
}
if (wfsLayers) {
options.wfsLayers = wfsLayers;
}
if (wfsUrl) {
options.wfsUrl = wfsUrl;
}
if (wmsIsSingleTile) {
options.wmsIsSingleTile = wmsIsSingleTile;
}
if (wmsLayers) {
options.wmsLayers = wmsLayers;
}
if (wmsUrl) {
options.wmsUrl = wmsUrl;
}
Expand Down
3 changes: 3 additions & 0 deletions contribs/gmf/src/datasource/OGC.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ import ngeoDatasourceOGC from 'ngeo/datasource/OGC.js';
* @property {string} [ogcType] The type data source. Can be: 'WMS' or 'WMTS'.
* @property {?Object<string, Object<string, import('gmf/themes.js').GmfOgcServerAttribute>>} [ogcAttributes]
* The attributes of the OGC server.
* @property {number[]} [queryIconPosition] values to define the shape (bbox) to use to query
* the layer. The values are used like a padding in css with 1, 2, 3 or 4 comma separated
* values: all / top-bottom, left-right / top, right-left, bottom / top, right, bottom, left.
* @property {boolean} [snappable] Whether the geometry from this data source can be used to snap the geometry
* of features from other data sources that are being edited. Defaults to `false`.
* @property {boolean} [snappingToEdges] Determines whether external features can be snapped to the edges of
Expand Down
3 changes: 3 additions & 0 deletions contribs/gmf/src/themes.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,9 @@
* @property {number} [minQueryResolution] The min resolution where the layer is queryable. For WMTS layers.
* @property {number} [minResolution] The min resolution where the layer is visible. For WMS layers.
* On WMTS layers it will have an effect on the node in the layertree but not on the layer directly.
* @property {number[]} [queryIconPosition] values to define the shape (bbox) to use to query
* the layer. The values are used like a padding in css with 1, 2, 3 or 4 comma separated
* values: all / top-bottom, left-right / top, right-left, bottom / top, right, bottom, left.
* @property {string} [ogcServer] The corresponding OGC server for a WMTS layer. For WMTS layers.
* @property {number} [opacity=1.0] Layer opacity. 1.0 means fully visible, 0 means invisible, For WMS and
* WMTS layers.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
"moment": "2.29.1",
"node-sass": "4.14.1",
"node-sass-importer": "1.0.0",
"ol": "6.4.3",
"ol": "6.4.4-dev.1606258895715",
"ol-cesium": "2.11.3",
"ol-layerswitcher": "3.8.1",
"parse-absolute-css-unit": "1.0.2",
Expand Down
18 changes: 18 additions & 0 deletions src/datasource/OGC.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ export const WMSInfoFormat = {
* @property {string} [ogcType] The type data source. Can be: 'WMS' or 'WMTS'.
* @property {?Object<string, Object<string, import('gmf/themes.js').GmfOgcServerAttribute>>} [ogcAttributes]
* The attributes of the OGC server.
* @property {number[]} [queryIconPosition] values to define the shape (bbox) to use to query
* the layer. The values are used like a padding in css with 1, 2, 3 or 4 comma separated
* values: all / top-bottom, left-right / top, right-left, bottom / top, right, bottom, left.
* @property {boolean} [snappable] Whether the geometry from this data source can be used to snap the geometry
* of features from other data sources that are being edited. Defaults to `false`.
* @property {boolean} [snappingToEdges] Determines whether external features can be snapped to the edges of
Expand Down Expand Up @@ -339,6 +342,14 @@ export class OGC extends ngeoDatasourceDataSource {
*/
this.ogcAttributes_ = options.ogcAttributes;

/**
* Values to define the shape (bbox) to use to query the layer. The values work like a css
* padding.
* @type {number[]}
* @private
*/
this.queryIconPosition_ = options.queryIconPosition;

/**
* Whether the geometry from this data source can be used to snap the
* geometry of features from other data sources that are being edited.
Expand Down Expand Up @@ -678,6 +689,13 @@ export class OGC extends ngeoDatasourceDataSource {
return this.ogcType_;
}

/**
* @return {number[]} The queryIconPosition
*/
get queryIconPosition() {
return this.queryIconPosition_;
}

/**
* @return {boolean} Snappable
*/
Expand Down
3 changes: 2 additions & 1 deletion src/filter/RuleHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,9 +468,10 @@ export class RuleHelper {
*/
createFilterString(options) {
let filterString = null;
const defaultWFSVersion = '1.1.0';
const filter = this.createFilter(options);
if (filter) {
const filterNode = writeFilter(filter);
const filterNode = writeFilter(filter, defaultWFSVersion);
const xmlSerializer = new XMLSerializer();
filterString = xmlSerializer.serializeToString(filterNode);
}
Expand Down
4 changes: 2 additions & 2 deletions src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ export function buildStyle(styleDescriptor) {
* Use this if you have more than one source bound to a layer.
* @property {number} [tolerance=3] When issuing an identify feature request at a click position, either a
* WMS GetFeatureInfo or a WFS GetFeature request will be used. For GetFeature requests a bbox is built
* around the position. This `tolerance` in pixel determines the size of the bbox.
* @property {number} [toleranceTouch=10] The tolerance on touch devices.
* around the position. This `tolerance` in pixel determines the minimal size of the bbox.
* @property {number} [toleranceTouch=10] Same as `tolerance` but for touch devices.
* @property {string} [featureNS='http://mapserver.gis.umn.edu/mapserver'] The feature namespace for WFS
* GetFeature requests.
* @property {string} [featurePrefix='feature'] The feature prefix for WFS GetFeature requests.
Expand Down
8 changes: 4 additions & 4 deletions src/query/MapQuerent.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,12 @@ export class MapQuerent {
* @type {number}
* @private
*/
this.tolerancePx_;
this.tolerance_;

if (hasCoarsePointingDevice()) {
this.tolerancePx_ = options.toleranceTouch !== undefined ? options.toleranceTouch : 10;
this.tolerance_ = options.toleranceTouch !== undefined ? options.toleranceTouch : 10;
} else {
this.tolerancePx_ = options.tolerance !== undefined ? options.tolerance : 3;
this.tolerance_ = options.tolerance !== undefined ? options.tolerance : 3;
}

/**
Expand Down Expand Up @@ -173,7 +173,7 @@ export class MapQuerent {
Object.assign(options, {
queryableDataSources: queryableDataSources,
limit: limit,
tolerancePx: this.tolerancePx_,
tolerance: this.tolerance_,
wfsCount: this.queryCountFirst_,
bboxAsGETParam: this.bboxAsGETParam_,
});
Expand Down
107 changes: 87 additions & 20 deletions src/query/Querent.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ import olSourceImageWMS from 'ol/source/ImageWMS.js';
* - `replace`: newly queried features are used as result
* - `add`: newly queried features are added to the existing ones
* - `remove`: newly queried features are removed from the existing ones
* @property {import("ol/extent.js").Extent} [bbox] The bbox to issue the requests with,
* which will end up with a WFS request.
* @property {import("ol/coordinate.js").Coordinate} [coordinate] The coordinate to issue the requests with,
* which can end up with either WMS or WFS requests.
* @property {Array<import('ngeo/datasource/DataSource.js').default>} [dataSources] list of data sources to
Expand All @@ -87,9 +85,8 @@ import olSourceImageWMS from 'ol/source/ImageWMS.js';
* @property {QueryableDataSources} [queryableDataSources] A hash of queryable data sources, which must meet
* all requirements. The querent service requires either the `dataSources` or `queryableDataSources`
* property to be set.
*
* @property {number} [tolerancePx] A tolerance value in pixels used to create an extent from a coordinate
* to issue WFS requests.
* @property {number} [tolerance] A minimal buffer value in pixels to ensure a minimal bbox around a
* coordinate to issue WFS requests.
* @property {boolean} [wfsCount] When set, before making WFS GetFeature requests to fetch features,
* WFS GetFeature requests with `resultType = 'hits'` are made first. If
* the number of records for the request would exceed the limit, then
Expand Down Expand Up @@ -564,25 +561,28 @@ export class Querent {
}

// (1) Extent (bbox), which is optional, i.e. its value can stay undefined
/** @type {import("ol/extent.js").Extent} */
let bbox;
const coordinate = options.coordinate;
if (coordinate) {
const tolerancePx = options.tolerancePx;
console.assert(tolerancePx);
const tolerance = tolerancePx * resolution;
bbox = olExtent.buffer(olExtent.createOrUpdateFromCoordinate(coordinate), tolerance);
const tolerance = options.tolerance;
console.assert(tolerance);
bbox = olExtent.buffer(olExtent.createOrUpdateFromCoordinate(coordinate), tolerance * resolution);
} else {
bbox = options.extent;
}

// (2) Launch one request per combinaison of data sources
const wfsFormat = new olFormatWFS();
const xmlSerializer = new XMLSerializer();
let hasAtLeastOneQueryIconPosition = false;
for (const dataSources of combinedDataSources) {
/** @type {?import('ol/format/WFS.js').WriteGetFeatureOptions} */
let getFeatureCommonOptions = null;
/** @type {string[]} */
let featureTypes = [];
let featureTypesNames = [];
/** @type {import('ol/format/WFS.js').FeatureType[]} */
const featureTypesObjects = [];
/** @type {?string} */
let url = null;
/** @type {Object<string, string>} */
Expand All @@ -594,13 +594,13 @@ export class Querent {

// (3) Build query options
for (const dataSource of dataSources) {
const currentFeatureTypes = dataSource.getInRangeWFSLayerNames(resolution, true);
const currentFeatureTypesNames = dataSource.getInRangeWFSLayerNames(resolution, true);
const geometryName = dataSource.geometryName(currentFeatureTypesNames[0]);

// (a) Create common options, if not done yet
if (!getFeatureCommonOptions) {
const featureNS = dataSource.wfsFeatureNS;
const featurePrefix = dataSource.wfsFeaturePrefix;
const geometryName = dataSource.geometryName(currentFeatureTypes[0]);
const outputFormat = dataSource.wfsOutputFormat;
if (!geometryName) {
throw new Error('Missing geometryName');
Expand All @@ -622,8 +622,8 @@ export class Querent {
Object.assign(params, dataSource.activeDimensions);
}

// (b) Add queryable layer names in featureTypes array
featureTypes = featureTypes.concat(currentFeatureTypes);
// (b) Add queryable layer names in featureTypesNames array
featureTypesNames = featureTypesNames.concat(currentFeatureTypesNames);

// (c) Add filter, if any. If the case, then only one data source
// is expected to be used for this request.
Expand Down Expand Up @@ -659,23 +659,52 @@ export class Querent {
// create and add a spatial filter it to the existing filter
// as well.
if (options.geometry) {
const spatialFilter = olFormatFilter.intersects(
dataSource.geometryName(),
options.geometry,
srsName
);
const spatialFilter = olFormatFilter.intersects(geometryName, options.geometry, srsName);
filter = this.ngeoRuleHelper_.joinFilters(filter, spatialFilter);
}

if (filter) {
getFeatureCommonOptions.filter = filter;
}

// (e) For coordinate (click) query, and if at least one dataSource has a
// queryIconPosition, define featureTypes as Object to use a custom bbox per layer.
if (coordinate) {
/** @type {import("ol/extent.js").Extent} */
let queryIconPosition;
if (dataSource.queryIconPosition) {
hasAtLeastOneQueryIconPosition = true;
queryIconPosition = this.makeBboxWithQueryIconPosition_(
dataSource.queryIconPosition,
resolution,
coordinate
);
console.assert(queryIconPosition !== null, 'Bad queryIconPosition values');
// Be sure it respects a minimal bbox.
queryIconPosition = olExtent.extend(queryIconPosition, bbox);
}
currentFeatureTypesNames.forEach((name) => {
featureTypesObjects.push({
geometryName,
name,
bbox: queryIconPosition || bbox,
});
});
}
}

if (!getFeatureCommonOptions) {
throw new Error('Missing getFeatureCommonOptions');
}

getFeatureCommonOptions.featureTypes = featureTypes;
if (hasAtLeastOneQueryIconPosition) {
getFeatureCommonOptions.featureTypes = featureTypesObjects;
// If featureTypes is set with FeatureType objects then bbox and geometryName is
// not used. Delete them for clarity.
delete getFeatureCommonOptions.bbox;
delete getFeatureCommonOptions.geometryName;
}

if (!url) {
throw new Error('Missing url');
}
Expand Down Expand Up @@ -771,6 +800,44 @@ export class Querent {
return this.q_.all(promises).then(handleCombinedQueryResult_);
}

/**
* Create and add a buffer around the given coordinate.
* The buffer is built with the flipped (horizontally and vertically) values of the queryIconPosition.
* @param {!number[]} queryIconPosition The values in px to buffer the bbox (1 to 4 values, css system).
* @param {!number} resolution The map view resolution to define the px size correctly.
* @param {!import("ol/coordinate.js").Coordinate} coordinate The bbox to buffer.
* @return {!import("ol/extent.js").Extent} The new bbox or null if the queryIconPosition param
* is not valid.
* @private
*/
makeBboxWithQueryIconPosition_(queryIconPosition, resolution, coordinate) {
const bbox = olExtent.createOrUpdateFromCoordinate(coordinate);
const buffers = queryIconPosition.map((value) => value * resolution);
const length = buffers.length;
if (!length || length > 4) {
return null;
}
if (length === 1) {
// Same buffer all around.
return olExtent.buffer(bbox, buffers[0]);
}
const fourValuesBuffers = [
buffers[0], // Top is always set.
buffers[1], // Right is always set (with length > 1).
length === 2 ? buffers[0] : buffers[2], // Take bottom.
length === 4 ? buffers[3] : buffers[1], // Take left.
];
// To includes the feature's point into the queried zone relative to the click. Flip vertically and
// horizontally the queryIconPosition (that is relative to the icon).
// Ol extent coordinate order is [left, bottom, right, top].
return [
bbox[0] - fourValuesBuffers[1], // Use right buffer for left.
bbox[1] - fourValuesBuffers[0], // Use top buffer for bottom.
bbox[2] + fourValuesBuffers[3], // Use left buffer for right.
bbox[3] + fourValuesBuffers[2], // Use bottom buffer for top.
];
}

/**
* Issue WMS GetFeatureInfo requests using the given combined data sources,
* map and optional filters.
Expand Down
1 change: 1 addition & 0 deletions test/spec/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import './services/location.spec.js';
import './services/printutils.spec.js';
import './services/autoprojection.spec.js';
import './services/debounce.spec.js';
import './services/querent.spec.js';
import './ol-ext/format/featurehash.spec.js';
import './directives/filereader.spec.js';
import './directives/scaleselector.spec.js';
Expand Down
Loading

0 comments on commit 2b0c915

Please sign in to comment.