Skip to content

Commit

Permalink
Closes #2418 Added spatial filter customizable methods (#2431)
Browse files Browse the repository at this point in the history
  • Loading branch information
MV88 authored and offtherailz committed Nov 29, 2017
1 parent 1555801 commit ac62205
Show file tree
Hide file tree
Showing 22 changed files with 832 additions and 36 deletions.
1 change: 1 addition & 0 deletions docma-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@
"web/client/plugins/Login.jsx",
"web/client/plugins/MousePosition.jsx",
"web/client/plugins/Notifications.jsx",
"web/client/plugins/QueryPanel.jsx",
"web/client/plugins/ScaleBox.jsx",
"web/client/plugins/ScrollTop.jsx",
"web/client/plugins/Search.jsx",
Expand Down
4 changes: 4 additions & 0 deletions web/client/components/data/query/ComboField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class ComboField extends React.Component {
valueField: PropTypes.string,
textField: PropTypes.string,
placeholder: PropTypes.object,
itemComponent: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
fieldOptions: PropTypes.array,
fieldName: PropTypes.string,
fieldRowId: PropTypes.number,
Expand Down Expand Up @@ -74,6 +75,7 @@ class ComboField extends React.Component {
textField: null,
fieldOptions: [],
fieldName: null,
itemComponent: null,
fieldRowId: null,
fieldValue: null,
fieldException: null,
Expand All @@ -100,6 +102,7 @@ class ComboField extends React.Component {
{...this.props.options}
busy={this.props.busy}
disabled={this.props.disabled}
itemComponent={this.props.itemComponent}
valueField={this.props.valueField}
textField={this.props.textField}
data={this.props.fieldOptions}
Expand All @@ -119,6 +122,7 @@ class ComboField extends React.Component {
{...this.props.options}
busy={this.props.busy}
disabled={this.props.disabled}
itemComponent={this.props.itemComponent}
data={this.props.fieldOptions}
value={this.props.fieldValue}
caseSensitive={false}
Expand Down
27 changes: 27 additions & 0 deletions web/client/components/data/query/ComboFieldListItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2017, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/


const React = require('react');
const PropTypes = require('prop-types');
class ComboFieldListItem extends React.Component {
static propTypes = {
item: PropTypes.string,
textField: PropTypes.string,
customItemClassName: PropTypes.string,
valueField: PropTypes.string
};

render() {
return (
<span className={this.props.customItemClassName ? this.props.customItemClassName : ""}>{this.props.item}</span>
);
}
}

module.exports = ComboFieldListItem;
75 changes: 58 additions & 17 deletions web/client/components/data/query/SpatialFilter.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const PropTypes = require('prop-types');
/**
* Copyright 2016, GeoSolutions Sas.
* All rights reserved.
Expand All @@ -7,12 +6,17 @@ const PropTypes = require('prop-types');
* LICENSE file in the root directory of this source tree.
*/
const React = require('react');
const PropTypes = require('prop-types');

const {Row, Col, Panel, Button, Glyphicon, FormControl} = require('react-bootstrap');
const ComboField = require('./ComboField');
const GeometryDetails = require('./GeometryDetails');
const {AutocompleteWFSCombobox} = require('../../misc/AutocompleteWFSCombobox');
const ComboFieldListItem = require('./ComboFieldListItem');
const {createWFSFetchStream} = require('../../../observables/autocomplete');

const ZoneField = require('./ZoneField');
const {find} = require('lodash');

const LocaleUtils = require('../../../utils/LocaleUtils');
const I18N = require('../../I18N/I18N');
Expand Down Expand Up @@ -59,6 +63,9 @@ class SpatialFilter extends React.Component {
}
};

getMethodFromId = (id) => {
return find(this.props.spatialMethodOptions, method => method && method.id === id) || null;
};
renderHeader = () => {
const spatialFilterHeader = LocaleUtils.getMessageById(this.context.messages, "queryform.spatialfilter.spatial_filter_header");

Expand All @@ -75,21 +82,23 @@ class SpatialFilter extends React.Component {
};

renderSpatialHeader = () => {
const selectedMethod = this.props.spatialMethodOptions.filter((opt) => this.props.spatialField.method === opt.id)[0];
const selectedMethod = this.getMethodFromId(this.props.spatialField.method);
// const selectedMethod = this.props.spatialMethodOptions.filter((opt) => this.props.spatialField.method === opt.id)[0];

const methodCombo =
(<ComboField
fieldOptions={
this.props.spatialMethodOptions.map((opt) => {
return LocaleUtils.getMessageById(this.context.messages, opt.name);
return LocaleUtils.getMessageById(this.context.messages, opt.name) || opt.name;
})
}
itemComponent={(other) => <ComboFieldListItem customItemClassName={this.getMethodFromId(other.item) && this.getMethodFromId(other.item).customItemClassName || ""} {...other}/>}
placeholder={LocaleUtils.getMessageById(this.context.messages, "queryform.spatialfilter.combo_placeholder")}
fieldName="method"
style={{width: "140px"}}
fieldRowId={new Date().getTime()}
fieldValue={
LocaleUtils.getMessageById(this.context.messages, selectedMethod ? selectedMethod.name : "")
LocaleUtils.getMessageById(this.context.messages, selectedMethod ? selectedMethod.name : "") || selectedMethod && selectedMethod.name || ""
}
onUpdateField={this.updateSpatialMethod}/>)
;
Expand Down Expand Up @@ -181,11 +190,38 @@ class SpatialFilter extends React.Component {
}) : <span/>;
};

renderRoiPanel = () => {
const selectedMethod = this.getMethodFromId(this.props.spatialField.method);
return (<Panel>
<div className="container-fluid">
<Row className="filter-field-row">
<Col xs={5}>
<span>{this.props.spatialField.method}</span>
</Col>
<Col xs={7}>
<div style={{width: "140px"}}>
<AutocompleteWFSCombobox
autocompleteStreamFactory={createWFSFetchStream}
valueField={selectedMethod && selectedMethod.filterProps && selectedMethod.filterProps.valueField}
textField={selectedMethod && selectedMethod.filterProps && selectedMethod.filterProps.valueField}
url={selectedMethod && selectedMethod.url}
filter="contains"
onChangeDrawingStatus={this.props.actions.onChangeDrawingStatus}
filterProps={selectedMethod && selectedMethod.filterProps}
/>
</div>

</Col>
</Row>
</div>
</Panel>);
};
renderSpatialPanel = (operationRow, drawLabel) => {
return (
<Panel>
{this.props.spatialMethodOptions.length > 1 ? this.renderSpatialHeader() : <span/>}
{this.renderZoneFields()}
{this.props.spatialField.method && this.getMethodFromId(this.props.spatialField.method) && this.getMethodFromId(this.props.spatialField.method).type === "wfsGeocoder" ? this.renderRoiPanel() : <span/>}
{this.props.spatialOperations.length > 1 ?
<Panel>
<div className="container-fluid">
Expand All @@ -208,7 +244,8 @@ class SpatialFilter extends React.Component {
const selectedOperation = this.props.spatialOperations.filter((opt) => this.props.spatialField.operation === opt.id)[0];

let drawLabel = <span/>;
if (this.props.spatialField.method && this.props.spatialField.method !== "ZONE" && this.props.spatialField.method !== "Viewport") {
if (this.props.spatialField.method !== "ZONE" && this.props.spatialField.method !== "Viewport" &&
this.getMethodFromId(this.props.spatialField.method) && this.getMethodFromId(this.props.spatialField.method).type !== "wfsGeocoder") {
drawLabel = !this.props.spatialField.geometry ?
(<span>
<hr width="100%"/>
Expand Down Expand Up @@ -305,25 +342,29 @@ class SpatialFilter extends React.Component {
this.props.actions.onShowSpatialSelectionDetails(false);

const method = this.props.spatialMethodOptions.filter((opt) => {
if (value === LocaleUtils.getMessageById(this.context.messages, opt.name)) {
if (value === (LocaleUtils.getMessageById(this.context.messages, opt.name) || opt.name)) {
return opt;
}
})[0].id;

this.props.actions.onSelectSpatialMethod(method, name);

switch (method) {
case "ZONE": {
this.changeDrawingStatus('clean', null, "queryform", []); break;
}
case "Viewport": {
this.changeDrawingStatus('clean', null, "queryform", []);
this.props.actions.onSelectViewportSpatialMethod();
break;
}
default: {
this.changeDrawingStatus('start', method, "queryform", [], {stopAfterDrawing: true});
if (this.getMethodFromId(method).type !== "wfsGeocoder") {
switch (method) {
case "ZONE": {
this.changeDrawingStatus('clean', null, "queryform", []); break;
}
case "Viewport": {
this.changeDrawingStatus('clean', null, "queryform", []);
this.props.actions.onSelectViewportSpatialMethod();
break;
}
default: {
this.changeDrawingStatus('start', method, "queryform", [], {stopAfterDrawing: true});
}
}
} else {
this.changeDrawingStatus('clean', null, "queryform", []);
}
};

Expand Down
11 changes: 6 additions & 5 deletions web/client/components/map/leaflet/DrawSupport.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const {head} = require('lodash');
const L = require('leaflet');
require('leaflet-draw');
const {isSimpleGeomType, getSimpleGeomType} = require('../../../utils/MapUtils');
const {fromLeafletFeatureToQueryform, boundsToOLExtent} = require('../../../utils/DrawSupportUtils');
const assign = require('object-assign');

const CoordinatesUtils = require('../../../utils/CoordinatesUtils');
Expand Down Expand Up @@ -126,7 +127,7 @@ class DrawSupport extends React.Component {
} else {
bounds = layer.getBounds();
}
let extent = this.boundsToOLExtent(bounds);
let extent = boundsToOLExtent(bounds);
let center = bounds.getCenter();
center = [center.lng, center.lat];
let radius = layer.getRadius ? layer.getRadius() : 0;
Expand Down Expand Up @@ -345,6 +346,10 @@ class DrawSupport extends React.Component {
if (newProps.options.drawEnabled) {
this.addDrawInteraction(props);
}
if (newProps.options.updateSpatialField) {
const feature = fromLeafletFeatureToQueryform(this.drawLayer);
this.props.onEndDrawing(feature, this.props.drawOwner);
}
};

addEditInteraction = (newProps) => {
Expand Down Expand Up @@ -433,10 +438,6 @@ class DrawSupport extends React.Component {
}
};

boundsToOLExtent = (bounds) => {
return [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()];
};

convertFeaturesPolygonToPoint = (features, method) => {
return method === 'Circle' ? features.map((f) => {
return {...f, type: "Point"};
Expand Down
33 changes: 28 additions & 5 deletions web/client/components/map/leaflet/__tests__/DrawSupport-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
* LICENSE file in the root directory of this source tree.
*/

var expect = require('expect');
var React = require('react');
var ReactDOM = require('react-dom');
var L = require('leaflet');
var DrawSupport = require('../DrawSupport');
const expect = require('expect');
const React = require('react');
const ReactDOM = require('react-dom');
const L = require('leaflet');
const DrawSupport = require('../DrawSupport');
const {} = require('../../../../test-resources/drawsupport/features');
describe('Leaflet DrawSupport', () => {
var msNode;
Expand Down Expand Up @@ -268,5 +268,28 @@ describe('Leaflet DrawSupport', () => {
expect(cmp).toExist();

});
it('test updateSpatialField = true', () => {
const latlngs = [[37, -109.05], [41, -109.03], [41, -102.05], [37, -102.04]];
let map = L.map("map", {
center: [51.505, -0.09],
zoom: 13
});
let cmp = ReactDOM.render( <DrawSupport map={map} drawOwner="me" drawStatus="drawOrEdit" drawMethod="Polygon" features={[{
type: "Feature",
geometry: {
type: "Polygon",
coordinates: [latlngs]
}
}]} options={{drawEnabled: false, updateSpatialField: false}} />, msNode);
cmp = ReactDOM.render( <DrawSupport map={map} drawOwner="me" drawStatus="drawOrEdit" drawMethod="Polygon" features={[{
type: "Feature",
geometry: {
type: "Polygon",
coordinates: [latlngs]
}
}]} options={{drawEnabled: false, updateSpatialField: true}} />, msNode);
expect(cmp).toExist();

});

});
7 changes: 7 additions & 0 deletions web/client/components/map/openlayers/DrawSupport.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,13 @@ class DrawSupport extends React.Component {
if (newProps.options.drawEnabled) {
this.handleDrawAndEdit(newProps.drawMethod, newProps.options.startingPoint, newProps.options.maxPoints);
}
if (newProps.options.updateSpatialField) {
this.sketchFeature = this.drawSource.getFeatures()[0];
this.sketchFeature.set('id', uuid.v1());
const feature = this.fromOLFeature(this.sketchFeature);

this.props.onEndDrawing(feature, this.props.drawOwner);
}
};

addSelectInteraction = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -887,4 +887,65 @@ describe('Test DrawSupport', () => {
expect(spyChangeStatus.calls.length).toBe(1);
expect(spyChange.calls.length).toBe(1);
});
it('draw or edit, update spatial field', () => {
const fakeMap = {
addLayer: () => {},
removeLayer: () => {},
disableEventListener: () => {},
enableEventListener: () => {},
addInteraction: () => {},
removeInteraction: () => {},
getInteractions: () => ({
getLength: () => 0
}),
getView: () => ({
getProjection: () => ({
getCode: () => 'EPSG:4326'
})
})
};

const testHandlers = {
onEndDrawing: () => {},
onStatusChange: () => {},
onGeometryChanged: () => {}
};
const geoJSON = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [13, 43]
},
properties: {
'name': "some name"
}
};
const feature = new ol.Feature({
geometry: new ol.geom.Point(13.0, 43.0),
name: 'My Point'
});
const spyEnd = expect.spyOn(testHandlers, "onEndDrawing");
const spyChange = expect.spyOn(testHandlers, "onGeometryChanged");
const spyChangeStatus = expect.spyOn(testHandlers, "onStatusChange");

const support = ReactDOM.render(
<DrawSupport features={[]} map={fakeMap}/>, document.getElementById("container"));
expect(support).toExist();
ReactDOM.render(
<DrawSupport features={[geoJSON]} map={fakeMap} drawStatus="drawOrEdit" drawMethod="Point" options={{
drawEnabled: true,
updateSpatialField: true
}}
onEndDrawing={testHandlers.onEndDrawing}
onChangeDrawingStatus={testHandlers.onStatusChange}
onGeometryChanged={testHandlers.onGeometryChanged}
/>, document.getElementById("container"));
support.drawInteraction.dispatchEvent({
type: 'drawend',
feature: feature
});
expect(spyEnd.calls.length).toBe(1);
expect(spyChangeStatus.calls.length).toBe(1);
expect(spyChange.calls.length).toBe(1);
});
});
Loading

0 comments on commit ac62205

Please sign in to comment.