Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Click tolerence per layer #6445

Merged
merged 8 commits into from
Nov 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
ger-benjamin marked this conversation as resolved.
Show resolved Hide resolved
}
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