Skip to content

Commit

Permalink
Read and use the queryIconPosition layer metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
ger-benjamin committed Nov 16, 2020
1 parent 4be7954 commit bd9d7a6
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 20 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
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
82 changes: 71 additions & 11 deletions src/query/Querent.js
Original file line number Diff line number Diff line change
Expand Up @@ -576,11 +576,14 @@ export class Querent {
// (2) Launch one request per combinaison of data sources
const wfsFormat = new olFormatWFS();
const xmlSerializer = new XMLSerializer();
let hasOneQueryIconPosition = 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 @@ -592,13 +595,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 @@ -620,8 +623,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 @@ -657,23 +660,48 @@ export class Querent {
// create and add a spatial filter it to the existing filter
// as well.
if (options.geometry) {
const spatialFilter = olFormatFilter.intersects(
geometryName,
options.geometry,
srsName
);
const spatialFilter = olFormatFilter.intersects(geometryName, options.geometry, srsName);
filter = this.ngeoRuleHelper_.joinFilters(filter, spatialFilter);
}

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

// (e) Define featureTypes (objects) option with dataSource.queryIconPosition
// as bbox or the default bbox if the datasource don't have such option.
// This option will be added only if there is at least one dataSource with
// queryIconPosition.
let queryIconPosition;
if (dataSource.queryIconPosition) {
hasOneQueryIconPosition = true;
queryIconPosition = this.extendBboxWithQueryIconPosition_(
dataSource.queryIconPosition,
resolution,
bbox
);
console.assert(queryIconPosition !== null, 'Bad queryIconPosition values');
}
featureTypesObjects.push({
geometryName,
name: currentFeatureTypesNames,
bbox: queryIconPosition || bbox,
});
}

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

getFeatureCommonOptions.featureTypes = featureTypes;
if (hasOneQueryIconPosition) {
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;
}

getFeatureCommonOptions.featureTypes = featureTypesObjects;
if (!url) {
throw new Error('Missing url');
}
Expand Down Expand Up @@ -769,6 +797,38 @@ export class Querent {
return this.q_.all(promises).then(handleCombinedQueryResult_);
}

/**
* Extend the given bbox with the queryIconPosition values.
* @param {!number[]} queryIconPosition The values in px to buffer the bbox.
* @param {number} resolution The map view resolution to transform px to map distances.
* @param {!import("ol/extent.js").Extent} bbox The bbox to extend.
* @return {!import("ol/extent.js").Extent} The extended bbox or null if the queryIconPosition param
* is not valid.
* @private
*/
extendBboxWithQueryIconPosition_(queryIconPosition, resolution, bbox) {
const buffers = queryIconPosition.map((value) => value * resolution);
const length = buffers.length;
if (!length || length > 4) {
// Bad format.
return null;
}
if (length === 1) {
// Same buffer all around.
return olExtent.buffer(bbox, buffers[0]);
}
return [
// bbox[0] is top, always set with buffer[0];
bbox[0] - buffers[0], // bbox[0] is top, always set with buffer[0];
// bbox[1] is right, always set with buffer[1] for length > 1;
bbox[1] - buffers[1],
// bbox[2] is bottom. Length === 2 is top-bottom and right-left. Length > 2 defines the bottom value.
bbox[2] + (length === 2 ? buffers[0] : buffers[2]),
// bbox[3] is left. Length === 4 is each side defined and the only manner to define the left value.
bbox[3] + (length === 4 ? buffers[3] : buffers[1]),
];
}

/**
* 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
60 changes: 60 additions & 0 deletions test/spec/services/querent.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// The MIT License (MIT)
//
// Copyright (c) 2020 Camptocamp SA
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// @ts-nocheck
import angular from 'angular';

describe('ngeo.Querent', () => {
/** @type {import('ngeo/query/Querent').Querent} */
let ngeoQuerent;

beforeEach(() => {
angular.mock.inject((_ngeoQuerent_) => {
ngeoQuerent = _ngeoQuerent_;
});
});

it('Extends bbox with queryIconPosition', () => {
const resolution = 10;
const bbox = [50, 51, 52, 53];

let result = ngeoQuerent.extendBboxWithQueryIconPosition_([], resolution, bbox);
expect(result).toBe(null);

result = ngeoQuerent.extendBboxWithQueryIconPosition_([1], resolution, bbox);
expect(result).toEqual([40, 41, 62, 63]);

result = ngeoQuerent.extendBboxWithQueryIconPosition_([1, 2], resolution, bbox);
expect(result).toEqual([40, 31, 62, 73]);

result = ngeoQuerent.extendBboxWithQueryIconPosition_([1, 2, 3], resolution, bbox);
expect(result).toEqual([40, 31, 82, 73]);

result = ngeoQuerent.extendBboxWithQueryIconPosition_([1, 2, 3, 4], resolution, bbox);
expect(result).toEqual([40, 31, 82, 93]);

result = ngeoQuerent.extendBboxWithQueryIconPosition_([0, 0, 0, 4], resolution, bbox);
expect(result).toEqual([50, 51, 52, 93]);

result = ngeoQuerent.extendBboxWithQueryIconPosition_([1, 2, 3, 4, 5], resolution, bbox);
expect(result).toBe(null);
});
});

0 comments on commit bd9d7a6

Please sign in to comment.