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

Street View: Street Smart API support #9878

Merged
merged 10 commits into from
Jan 15, 2024
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
21 changes: 21 additions & 0 deletions docs/developer-guide/maps-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,27 @@ This layer differs from the "vector" because all the loading/filtering/querying
- `url`: the url of the WFS service.
- `fields`: if the layer has a wfs service configured, this can contain the fields (attributes) of the features, with custom configuration (e.g. aliases, types, etc.)

##### Experimental properties

Here a list of experimental properties that can be used in the WFS layer configuration, they may change in the future.

- `serverType`: analogous to the `serverType` of the WMS layer. It can be `geoserver` or `no-vendor`. If `no-vendor` is used, the requests will not contain vendor specific parameters like `cql_filter`, and the layer will be treated as a generic WFS layer. The filtering will be done on the "WFS client" side, using the `filter` property. Actually this property is supported only in OpenLayers and only for the map viewer.
- `security`: this is an object that can contain the security configuration for the layer. It allows to specify if some particular security is configured for this layer, apart from the usual `securityRules` configured globally. It can contain the following properties. Actually this property is supported only in OpenLayers and only for the map viewer:

- `type`: the type of security to use. It can be `Basic`.
- `sourceType`: the type of source to use. It can be `sessionStorage`. In case of sessionStorage, a `sourceId` must be provided to get the credentials from the sessionStorage.
- `sourceId`: the id of the source to use. It can be any string.

```json
{
"security": {
"type": "Basic",
"sourceType": "sessionStorage",
"sourceId": "source-identifier"
}
}
```

#### Graticule

i.e.
Expand Down
53 changes: 53 additions & 0 deletions web/client/api/WFS.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import axios from '../libs/ajax';
import urlUtil from 'url';
import assign from 'object-assign';
import requestBuilder from '../utils/ogc/WFS/RequestBuilder';
import {toOGCFilterParts} from '../utils/FilterUtils';

export const toDescribeURL = (url, typeName) => {
const parsed = urlUtil.parse(url, true);
Expand Down Expand Up @@ -65,7 +67,58 @@ export const getFeatureURL = (url, typeName, { version = "1.1.0", ...params } =
}, parsed.query)
}));
};
/**
* Performs a getFeature request (using axios POST) to a WFS service for a given MapStore layer WFS layer object.
* @param {object} layer MapStore layer object
* @param {object} requestOptions additional request options. Can include:
* - `version`: WFS version. Default: `1.1.0`
* - `filter`: optional array of mapstore filters. If the layer has a `layerFilter` property or filterObj, it will be added to the filter in a logic AND.
* - `proj`: projection string
* - `outputFormat`: output format string. Default: `application/json`
* - `resultType`: result type string. Default: `results`
* @param {object} config axios request config (headers, etc...)
* @returns
*/
export const getFeatureLayer = (layer, {version = "1.1.0", filters, proj, outputFormat = 'application/json', resultType = 'results'} = {}, config) => {
const {url, name: typeName, params } = layer;
const {layerFilter, filterObj: featureGridFilter} = layer; // TODO: add
const {getFeature: wfsGetFeature, query, filter, and} = requestBuilder({wfsVersion: version});
const allFilters = []
.concat(filters ?? [])
.concat(layerFilter ? layerFilter : [])
.concat(featureGridFilter ? featureGridFilter : []);
const reqBody = wfsGetFeature(query(
typeName,
allFilters.length > 0
? filter(
and(
allFilters
.map(f => toOGCFilterParts(f, version, "ogc"))
)

) : "",
{srsName: proj} // 3rd for query is optional
),
{outputFormat, resultType}
);
return axios.post(url, reqBody, {
...config,
params,
headers: {
...config?.headers,
'Content-Type': 'application/xml'
}
});
};

/**
* Performs a WFS GetFeature request (using axios GET) with the given parameters.
* @param {string} url URL of the WFS service
* @param {string} typeName layer name
* @param {object} params the params to add to the request
* @param {object} config axios request config (headers, etc...)
* @returns {Promise} the axios promise
*/
export const getFeature = (url, typeName, params, config) => {
return axios.get(getFeatureURL(url, typeName, params), config);
};
Expand Down
123 changes: 123 additions & 0 deletions web/client/api/__tests__/WFS-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import expect from 'expect';


import MockAdapter from 'axios-mock-adapter';
import axios from '../../libs/ajax';
import {
getFeatureLayer
} from '../WFS';

let mockAxios;

describe('Test WFS ogc API functions', () => {
beforeEach(done => {
mockAxios = new MockAdapter(axios);
setTimeout(done);
});

afterEach(done => {
mockAxios.restore();
setTimeout(done);
});
describe('getFeatureLayer', () => {
const schemaString = 'xmlns:gml="http://www.opengis.net/gml" '
+ 'xmlns:wfs="http://www.opengis.net/wfs" '
+ 'xmlns:ogc="http://www.opengis.net/ogc" '
+ 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
+ 'xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd" ';
it('getFeatureLayer', (done) => {
mockAxios.onPost().reply(({url, data, headers}) => {
expect(url).toContain('test');
expect(data).toEqual(
`<wfs:GetFeature service="WFS" version="1.1.0" `
+ schemaString
+ `resultType="results" outputFormat="application/json">`
+ `<wfs:Query typeName="layer1" srsName="EPSG:4326"></wfs:Query></wfs:GetFeature>`);
// check headers passed
expect(headers.Authentication).toBe('Basic token');
return [200, {
featureTypes: [{
name: 'layer1'
}]
}];
});
getFeatureLayer({
type: 'wfs',
url: 'test',
name: 'layer1'
}, {}, {
headers: {
'Authentication': 'Basic token'
}
}).then((data) => {
expect(data).toExist();
done();
});
});
it('getFeatureLayer with layerFilter', (done) => {
mockAxios.onPost().reply(({url, data}) => {
expect(url).toContain('test');
// some checks on the filter presence
expect(data).toContain('<ogc:Filter>');
expect(data).toContain('<ogc:PropertyIsEqualTo><ogc:PropertyName>prop</ogc:PropertyName><ogc:Literal>value</ogc:Literal></ogc:PropertyIsEqualTo>');
return [200, {
featureTypes: [{
name: 'layer1'
}]
}];
});
getFeatureLayer({
type: 'wfs',
url: 'test',
name: 'layer1',
layerFilter: {
filters: [{
format: 'cql',
body: "prop = 'value'"
}]
}
}).then((data) => {
expect(data).toExist();
done();
});
});
it('getFeatureLayer with layer filter and filter passed', (done) => {
mockAxios.onPost().reply(({url, data}) => {
expect(url).toContain('test');
// some checks on the filter presence
expect(data).toContain('<ogc:Filter><ogc:And>');
expect(data).toContain('<ogc:PropertyIsEqualTo><ogc:PropertyName>prop</ogc:PropertyName><ogc:Literal>value</ogc:Literal></ogc:PropertyIsEqualTo>');
expect(data).toContain('<ogc:PropertyIsEqualTo><ogc:PropertyName>prop2</ogc:PropertyName><ogc:Literal>value</ogc:Literal></ogc:PropertyIsEqualTo>');

return [200, {
featureTypes: [{
name: 'layer1'
}]
}];
});
getFeatureLayer({
type: 'wfs',
url: 'test',
name: 'layer1',
layerFilter: {
filters: [{
format: 'cql',
body: "prop = 'value'"
}]
}
}, {
filters: [{
// a mapstore filter
filters: [{
format: 'cql',
body: "prop2 = 'value'"
}]
}]
}).then((data) => {
expect(data).toExist();
done();
});
});
});

});
139 changes: 138 additions & 1 deletion web/client/components/map/openlayers/__tests__/Layer-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ import '../plugins/WFS3Layer';
import '../plugins/ElevationLayer';

import {
setStore
setStore,
setCredentials
} from '../../../../utils/SecurityUtils';
import ConfigUtils from '../../../../utils/ConfigUtils';
import { ServerTypes } from '../../../../utils/LayersUtils';


import { Map, View } from 'ol';
import { defaults as defaultControls } from 'ol/control';
Expand Down Expand Up @@ -3031,6 +3034,140 @@ describe('Openlayers layer', () => {
expect(layer.layer.getSource()).toBeTruthy();
});

describe('WFS', () => {
// this function create a WFS layer with the given options.
const createWFSLayerTest = (options, done, onRenderComplete = () => {}) => {
let layer;
map.on('rendercomplete', () => {
if (layer.layer.getSource().getFeatures().length > 0) {
const f = layer.layer.getSource().getFeatures()[0];
expect(f.getGeometry().getCoordinates()[0]).toBe(SAMPLE_FEATURE_COLLECTION.features[0].geometry.coordinates[0]);
expect(f.getGeometry().getCoordinates()[1]).toBe(SAMPLE_FEATURE_COLLECTION.features[0].geometry.coordinates[1]);
onRenderComplete(layer);
done();
}
});
// first render
layer = ReactDOM.render(<OpenlayersLayer
type="wfs"
options={{
...options
}}
map={map} />, document.getElementById("container"));
expect(layer.layer.getSource()).toBeTruthy();
};
describe('serverType: no-vendor', () => {
it('test basic post request', (done) => {
mockAxios.onPost().reply(({
url,
data,
method
}) => {
expect(url.indexOf('SAMPLE_URL') >= 0).toBeTruthy();
expect(method).toBe('post');
expect(data).toContain('<wfs:GetFeature');
expect(data).toContain('<wfs:Query typeName="osm:vector_tile"');
return [200, SAMPLE_FEATURE_COLLECTION];
});
createWFSLayerTest({
type: 'wfs',
visibility: true,
url: 'SAMPLE_URL',
name: 'osm:vector_tile',
serverType: ServerTypes.NO_VENDOR
}, done);
});
it('test layerFilter', (done) => {
mockAxios.onPost().reply(({
url,
data,
method
}) => {
expect(url.indexOf('SAMPLE_URL') >= 0).toBeTruthy();
expect(method).toBe('post');
expect(data).toContain('<wfs:GetFeature');
expect(data).toContain('<wfs:Query typeName="osm:vector_tile"');
expect(data).toContain(
'<ogc:Filter>'
+ '<ogc:And>'
+ '<ogc:PropertyIsEqualTo><ogc:PropertyName>a</ogc:PropertyName><ogc:Literal>1</ogc:Literal></ogc:PropertyIsEqualTo>'
+ '</ogc:And>'
+ '</ogc:Filter>');

return [200, SAMPLE_FEATURE_COLLECTION];
});
createWFSLayerTest({
type: 'wfs',
visibility: true,
url: 'SAMPLE_URL',
name: 'osm:vector_tile',
serverType: ServerTypes.NO_VENDOR,
layerFilter: {
filters: [{
format: 'cql',
body: 'a = 1'
}]
}
}, done);
});
it('test strategy "bbox"', (done) => {
mockAxios.onPost().reply(({
url,
data,
method
}) => {
expect(url.indexOf('SAMPLE_URL') >= 0).toBeTruthy();
expect(method).toBe('post');
expect(data).toContain('<wfs:GetFeature');
expect(data).toContain('<wfs:Query typeName="osm:vector_tile"');
expect(data).toContain('<ogc:PropertyIsEqualTo><ogc:PropertyName>a</ogc:PropertyName><ogc:Literal>1</ogc:Literal></ogc:PropertyIsEqualTo>');
expect(data).toContain('<ogc:BBOX>');

return [200, SAMPLE_FEATURE_COLLECTION];
});
createWFSLayerTest({
type: 'wfs',
visibility: true,
url: 'SAMPLE_URL',
strategy: 'bbox',
name: 'osm:vector_tile',
serverType: ServerTypes.NO_VENDOR,
layerFilter: {
filters: [{
format: 'cql',
body: 'a = 1'
}]
}
}, done);
});
it('test security basic authentication', (done) => {
mockAxios.onPost().reply(({
url,
method,
headers
}) => {
expect(url.indexOf('SAMPLE_URL') >= 0).toBeTruthy();
expect(method).toBe('post');
expect(headers.Authorization).toEqual(`Basic ${btoa("test:test")}`);
return [200, SAMPLE_FEATURE_COLLECTION];
});
setCredentials("TEST_SOURCE", {username: "test", password: "test"});
createWFSLayerTest({
type: 'wfs',
visibility: true,
url: 'SAMPLE_URL',
name: 'osm:vector_tile',
serverType: ServerTypes.NO_VENDOR,
security: {
type: 'basic',
sourceId: 'TEST_SOURCE'
}
}, done, () => {
setCredentials("TEST_SOURCE", undefined);
});
});
});
});
it('should apply native ol min and max resolution on vector layer', () => {
const minResolution = 1222; // ~ zoom 7 Web Mercator
const maxResolution = 39135; // ~ zoom 2 Web Mercator
Expand Down
Loading
Loading