Skip to content

Commit 3082f80

Browse files
authored
#12555 WFS export by current viewport doesn't work as expected (#12557)
* fix: add viewport filter support to WFS export in startFeatureExportDownload * Add support for reading .prj files and update shpToGeoJSON to handle projections * Fix viewportFilter assignment in startFeatureExportDownload * Fix axis extraction logic in getPrjAxisDirections function * Revert axis swapping and removing readShapePrjFiles * remove unused import and whitespace * fix: update viewportFilter assignment in startFeatureExportDownload function
1 parent 2b3d4b6 commit 3082f80

2 files changed

Lines changed: 136 additions & 6 deletions

File tree

web/client/epics/__tests__/layerdownload-test.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,100 @@ describe('layerdownload Epics', () => {
9898
state
9999
);
100100
});
101+
it('startFeatureExportDownload adds viewport filter to WFS export when cropDataSet is enabled', (done) => {
102+
const epicResult = actions => {
103+
expect(actions.length).toBe(1);
104+
expect(actions[0].error.config.url).toExist();
105+
expect(actions[0].error.config.url.indexOf("test-format") > 0).toBe(true);
106+
expect(actions[0].error.config.data.indexOf("<ogc:Intersects>") > 0).toBe(true);
107+
expect(actions[0].error.config.data.indexOf("<ogc:PropertyName>the_geom</ogc:PropertyName>") > 0).toBe(true);
108+
expect(actions[0].error.config.data.indexOf('<gml:Polygon srsName="EPSG:3857">') > 0).toBe(true);
109+
expect(actions[0].error.config.data.indexOf("<gml:posList>0 0 0 1 1 1 1 0 0 0</gml:posList>") > 0).toBe(true);
110+
done();
111+
};
112+
113+
mockAxios.onGet().reply(404);
114+
const state = {
115+
controls: {
116+
queryPanel: { enabled: false },
117+
layerdownload: { enabled: true }
118+
},
119+
featuregrid: {},
120+
layers: {
121+
flat: [{ id: 'test layer', name: 'test', layerFilter: { featureTypeName: 'test' } }],
122+
selected: ['test layer']
123+
},
124+
map: {
125+
present: {
126+
bbox: {
127+
bounds: { minx: 0, miny: 0, maxx: 1, maxy: 1 },
128+
crs: 'EPSG:3857'
129+
}
130+
}
131+
},
132+
query: {
133+
featureTypes: {
134+
test: {
135+
original: {
136+
featureTypes: [{
137+
properties: [{
138+
name: 'the_geom',
139+
type: 'gml:MultiPolygon'
140+
}]
141+
}]
142+
}
143+
}
144+
}
145+
}
146+
};
147+
testEpic(
148+
startFeatureExportDownload,
149+
1,
150+
downloadFeatures('/wrong/path?', { featureTypeName: 'test' }, { selectedFormat: "test-format", cropDataSet: true}),
151+
epicResult,
152+
state
153+
);
154+
});
155+
it('startFeatureExportDownload adds viewport BBOX filter when cropDataSet is enabled without describe metadata', (done) => {
156+
const epicResult = actions => {
157+
expect(actions.length).toBe(1);
158+
expect(actions[0].error.config.url).toExist();
159+
expect(actions[0].error.config.url.indexOf("test-format") > 0).toBe(true);
160+
expect(actions[0].error.config.data.indexOf("<ogc:BBOX>") > 0).toBe(true);
161+
expect(actions[0].error.config.data.indexOf('<gml:Envelope srsName="EPSG:3857">') > 0).toBe(true);
162+
expect(actions[0].error.config.data.indexOf("<gml:lowerCorner>0 0</gml:lowerCorner>") > 0).toBe(true);
163+
expect(actions[0].error.config.data.indexOf("<gml:upperCorner>1 1</gml:upperCorner>") > 0).toBe(true);
164+
};
165+
166+
mockAxios.onGet().reply(404);
167+
const state = {
168+
controls: {
169+
queryPanel: { enabled: false },
170+
layerdownload: { enabled: true }
171+
},
172+
featuregrid: {},
173+
layers: {
174+
flat: [{ id: 'test layer', name: 'test', layerFilter: { featureTypeName: 'test' } }],
175+
selected: ['test layer']
176+
},
177+
map: {
178+
present: {
179+
bbox: {
180+
bounds: { minx: 0, miny: 0, maxx: 1, maxy: 1 },
181+
crs: 'EPSG:3857'
182+
}
183+
}
184+
}
185+
};
186+
testEpic(
187+
startFeatureExportDownload,
188+
1,
189+
downloadFeatures('/wrong/path?', { featureTypeName: 'test' }, { selectedFormat: "test-format", cropDataSet: true}),
190+
epicResult,
191+
state,
192+
done
193+
);
194+
});
101195
it('startFeatureExportDownload cql_filter support', (done) => {
102196
const epicResult = actions => {
103197
expect(actions.length).toBe(1);

web/client/epics/layerdownload.js

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,11 @@ const hasOutputFormat = (data) => {
101101
return toPairs(pickedObj).map(([prop, value]) => ({ name: prop, label: value }));
102102
};
103103

104-
const getWFSFeature = ({ url, filterObj = {}, layerFilter, layer, downloadOptions = {}, options } = {}) => {
104+
const getWFSFeature = ({ url, filterObj = {}, layerFilter, layer, viewportFilter, downloadOptions = {}, options } = {}) => {
105105
const { sortOptions, propertyNames } = options;
106106

107107
const cqlFilter = getCQLFilterFromLayer(layer);
108-
const data = mergeFiltersToOGC({ ogcVersion: '1.1.0', addXmlnsToRoot: true, xmlnsToAdd: ['xmlns:ogc="http://www.opengis.net/ogc"', 'xmlns:gml="http://www.opengis.net/gml"'] }, downloadOptions.downloadFilteredDataSet ? layerFilter : {}, downloadOptions.downloadFilteredDataSet ? filterObj : {}, cqlFilter);
108+
const data = mergeFiltersToOGC({ ogcVersion: '1.1.0', addXmlnsToRoot: true, xmlnsToAdd: ['xmlns:ogc="http://www.opengis.net/ogc"', 'xmlns:gml="http://www.opengis.net/gml"'] }, downloadOptions.downloadFilteredDataSet ? layerFilter : {}, downloadOptions.downloadFilteredDataSet ? filterObj : {}, viewportFilter, cqlFilter);
109109

110110
return getXMLFeature(url, getFilterFeature(query(
111111
filterObj.featureTypeName, [...(sortOptions ? [sortBy(sortOptions.sortBy, sortOptions.sortOrder)] : []), ...(propertyNames ? [propertyName(propertyNames)] : []), ...(data ? castArray(data) : [])],
@@ -123,11 +123,45 @@ const getFileName = action => {
123123
return name;
124124
};
125125
const getDefaultSortOptions = (attribute) => {
126-
return attribute ? { sortBy: attribute, sortOrder: 'A'} : {};
126+
return attribute ? { sortBy: attribute, sortOrder: 'A'} : null;
127127
};
128128
const getFirstAttribute = (state)=> {
129129
return state.query && state.query.featureTypes && state.query.featureTypes[state.query.typeName] && state.query.featureTypes[state.query.typeName].attributes && state.query.featureTypes[state.query.typeName].attributes[0] && state.query.featureTypes[state.query.typeName].attributes[0].attribute || null;
130130
};
131+
const getBboxExtent = (bounds = {}) => {
132+
return Array.isArray(bounds)
133+
? bounds
134+
: [bounds.minx, bounds.miny, bounds.maxx, bounds.maxy];
135+
};
136+
const getViewportFilter = (cropDataSet, mapBbox, geometryAttribute) => {
137+
if (!cropDataSet || !mapBbox?.bounds) {
138+
return null;
139+
}
140+
const projection = mapBbox.crs || 'EPSG:4326';
141+
if (geometryAttribute) {
142+
return {
143+
spatialField: {
144+
geometry: {
145+
...bboxToFeatureGeometry(mapBbox.bounds),
146+
projection
147+
},
148+
attribute: geometryAttribute,
149+
method: 'Rectangle',
150+
operation: 'INTERSECTS'
151+
}
152+
};
153+
}
154+
return {
155+
spatialField: {
156+
geometry: {
157+
extent: [getBboxExtent(mapBbox.bounds)],
158+
projection
159+
},
160+
method: 'BBOX',
161+
operation: 'BBOX'
162+
}
163+
};
164+
};
131165

132166
const wpsExecuteErrorToMessage = e => {
133167
switch (e.code) {
@@ -255,11 +289,11 @@ export const startFeatureExportDownload = (action$, store) =>
255289

256290
const mapBbox = mapBboxSelector(state);
257291
const currentLocale = currentLocaleSelector(state);
292+
const geometryAttribute = extractGeometryAttributeName(layerDescribeSelector(state, layer.name));
258293
const propertyNames = action.downloadOptions.propertyName ? [
259-
extractGeometryAttributeName(layerDescribeSelector(state, layer.name)),
294+
...(geometryAttribute ? [geometryAttribute] : []),
260295
...action.downloadOptions.propertyName
261296
] : null;
262-
263297
const { layerFilter } = layer;
264298

265299
const wfsFlow = () => getWFSFeature({
@@ -268,6 +302,7 @@ export const startFeatureExportDownload = (action$, store) =>
268302
filterObj: isNil(action.filterObj) ? {} : action.filterObj,
269303
layer,
270304
layerFilter,
305+
viewportFilter: getViewportFilter(action.downloadOptions.cropDataSet, mapBbox, geometryAttribute),
271306
options: {
272307
pagination: !virtualScroll && get(action, "downloadOptions.singlePage") ? action.filterObj && action.filterObj.pagination : null,
273308
propertyNames
@@ -289,11 +324,12 @@ export const startFeatureExportDownload = (action$, store) =>
289324
filterObj: action.filterObj,
290325
layer,
291326
layerFilter,
327+
viewportFilter: getViewportFilter(action.downloadOptions.cropDataSet, mapBbox, geometryAttribute),
292328
options: {
293329
pagination: !virtualScroll && get(action, "downloadOptions.singlePage") ? action.filterObj && action.filterObj.pagination : null,
294330
sortOptions: getDefaultSortOptions(getFirstAttribute(store.getState())),
295331
propertyNames: action.downloadOptions.propertyName ? [...action.downloadOptions.propertyName,
296-
extractGeometryAttributeName(layerDescribeSelector(state, layer.name))] : null
332+
...(geometryAttribute ? [geometryAttribute] : [])] : null
297333
}
298334
}).do(({ data, headers }) => {
299335
if (headers["content-type"] === "application/xml") { // TODO add expected mimetypes in the case you want application/dxf

0 commit comments

Comments
 (0)