Skip to content

Commit

Permalink
Add FeatureSearch plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
manisandro committed Oct 16, 2023
1 parent 64570d6 commit f33407f
Show file tree
Hide file tree
Showing 22 changed files with 377 additions and 1 deletion.
182 changes: 182 additions & 0 deletions plugins/FeatureSearch.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/**
* Copyright 2023 Sourcepole AG
* 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.
*/


import React from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {v1 as uuidv1} from 'uuid';
import isEmpty from 'lodash.isempty';
import SideBar from '../components/SideBar';
import CoordinatesUtils from '../utils/CoordinatesUtils';
import IdentifyUtils from '../utils/IdentifyUtils';
import LocaleUtils from '../utils/LocaleUtils';
import "./style/FeatureSearch.css";
import IdentifyViewer from '../components/IdentifyViewer';

class FeatureSearch extends React.Component {
static propTypes = {
map: PropTypes.object,
/** The side of the application on which to display the sidebar. */
side: PropTypes.string,
theme: PropTypes.object
};
static defaultProps = {
side: 'right'
};
state = {
busy: false,
searchProviders: [],
selectedProvider: '',
searchResults: null
};
componentDidUpdate(prevProps) {
if (this.props.theme !== prevProps.theme) {
const searchProviders = (this.props.theme?.searchProviders || []).reduce((res, entry) => {
if (entry.provider === "qgis" && entry.params) {
const providerDef = {...entry};
if (!providerDef.params.fields) {
providerDef.params = {...providerDef.params};
providerDef.params.fields = {
TEXT: {label: LocaleUtils.tr("featuresearch.query"), type: "text"}
};
}
res[uuidv1()] = providerDef;
}
return res;
}, {});
this.setState({searchProviders: searchProviders});
}
}
onHide = () => {
this.setState({searchResults: null});
};
render() {
return (
<SideBar icon="search" id="FeatureSearch" onHide={this.onHide} side={this.props.side}
title={LocaleUtils.trmsg("featuresearch.title")} width="20em">
{() => ({
body: this.renderBody()
})}
</SideBar>
);
}
renderBody = () => {
return (
<div className="feature-search-body">
<div className="feature-search-selection">
<select onChange={this.selectProvider} value={this.state.selectedProvider}>
<option disabled value="">{LocaleUtils.tr("featuresearch.select")}</option>
{Object.entries(this.state.searchProviders).map(([key, entry]) => (
<option key={key} value={key}>{entry.params.title || LocaleUtils.tr(entry.params.titlemsgid)}</option>
))}
</select>
</div>
{this.renderSearchForm()}
{this.renderSearchResults()}
</div>
);
};
renderSearchForm = () => {
const provider = this.state.searchProviders[this.state.selectedProvider];
if (!provider) {
return null;
}
return (
<form className="feature-search-form" disabled={this.state.busy} onChange={() => this.setState({searchResults: null})} onSubmit={this.search}>
<fieldset disabled={this.state.busy}>
<table><tbody>
{Object.entries(provider.params.fields).map(([key, value]) => (
<tr key={key}>
<td>{value.label || LocaleUtils.tr(value.labelmsgid)}:</td>
<td>{this.renderField(key, value)}</td>
</tr>
))}
</tbody></table>
</fieldset>
<div className="feature-search-bar">
<button className="button" disabled={this.state.busy} type="submit">{LocaleUtils.tr("search.search")}</button>
</div>
</form>
);
};
renderField = (fieldname, fieldcfg) => {
if (fieldcfg.type === "number") {
return (<input name={fieldname} type="number" {...fieldcfg.options} />);
} else {
return (<input name={fieldname} type="text" />);
}
};
renderSearchResults = () => {
if (!this.state.searchResults) {
return null;
}
return (
<div className="feature-search-results">
{isEmpty(this.state.searchResults) ? (
<div className="feature-search-noresults">{LocaleUtils.tr("featuresearch.noresults")}</div>
) : (
<IdentifyViewer collapsible displayResultTree={false} identifyResults={this.state.searchResults} />
)}
</div>
);
};
selectProvider = (ev) => {
this.setState({selectedProvider: ev.target.value, searchResults: null});
};
search = (ev) => {
ev.preventDefault();
const provider = this.state.searchProviders[this.state.selectedProvider];
if (!provider) {
return;
}
const form = ev.target;
const filter = {...provider.params.expression};
const values = {};
Object.keys(provider.params.fields).forEach(fieldname => {
values[fieldname] = form.elements[fieldname].value;
});
const bbox = CoordinatesUtils.reprojectBbox(this.props.theme.bbox.bounds, this.props.theme.bbox.crs, this.props.theme.mapCrs);
const params = {
SERVICE: 'WMS',
VERSION: this.props.theme.version,
REQUEST: 'GetFeatureInfo',
CRS: this.props.theme.mapCrs,
BBOX: bbox.join(","),
WIDTH: 100,
HEIGHT: 100,
LAYERS: [],
FILTER: [],
WITH_GEOMETRY: true,
WITH_MAPTIP: false,
feature_count: 100
};
Object.keys(filter).forEach(layer => {
Object.entries(values).forEach(([key, value]) => {
filter[layer] = filter[layer].replace(`$${key}$`, value.replace("'", "\\'"));
});
params.LAYERS.push(layer);
params.FILTER.push(layer + ":" + filter[layer]);
});
params.LAYERS = params.LAYERS.join(",");
params.FILTER = params.FILTER.join(";");
this.setState({busy: true});
axios.get(this.props.theme.featureInfoUrl, {params}).then(response => {
const results = IdentifyUtils.parseResponse(response.data, null, 'text/xml', null, this.props.map.projection);
this.setState({busy: false, searchResults: results});
}).catch(() => {
this.setState({busy: false, searchResults: {}});
});
};
}

export default connect((state) => ({
map: state.map,
theme: state.theme.current
}), {})(FeatureSearch);
59 changes: 59 additions & 0 deletions plugins/style/FeatureSearch.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
div#FeatureSearch div.sidebar-body {
overflow: hidden;
}

div.feature-search-body {
/* topbar: 3.5em */
/* bottombar is high 3em at 75% font size, hence at 100% font size it is 0.75 * 3em = 2.25em */
/* titlebar: 2.5em */
max-height: calc(100vh - 8.25em); /* viewport - topbar - bottombar - sidebar_titlebar*/
max-height: calc(var(--vh, 1vh) * 100 - 8.25em); /* viewport - topbar - bottombar - sidebar_titlebar*/

padding: 0.25em;
display: flex;
flex-direction: column;
}

div.feature-search-selection {
flex: 0 0 auto;
}

div.feature-search-selection > select {
width: 100%;
}

form.feature-search-form {
flex: 0 0 auto;
}

form.feature-search-form > fieldset {
margin: 0;
padding: 0.25em;
border-color: var(--border-color);
border-style: solid;
border-width: 0 1px 0 1px;
}

form.feature-search-form > fieldset table {
width: 100%;
}
form.feature-search-form > fieldset td > input {
width: 100%;
}

div.feature-search-bar > button {
width: 100%;
}

div.feature-search-results {
flex: 1 1 auto;
overflow-y: auto;
}

div.feature-search-noresults {
font-style: italic;
padding: 0.25em;
margin-top: 0.25em;
border: 1px solid var(--border-color);
color: var(--text-color-disabled);
}
4 changes: 4 additions & 0 deletions selectors/searchproviders.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export default (searchProviders) => createSelector(
const themeProviders = theme && theme.current && theme.current.searchProviders ? theme.current.searchProviders : [];
const providerKeys = new Set();
for (const entry of themeProviders) {
// Omit qgis provider with field configuration, this is only supported through the FeatureSearch plugin
if (entry.provider === 'qgis' && entry?.params?.fields) {
continue;
}
// "key" is the legacy name for "provider"
const provider = searchProviders[entry.provider ?? entry.key ?? entry];
if (provider) {
Expand Down
7 changes: 7 additions & 0 deletions translations/ca-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"DxfExport": "Exportar DXF",
"Editing": "Editant",
"FeatureForm": "Formulari d'element",
"FeatureSearch": "",
"Help": "Ajuda",
"IdentifyPoint": "Identificar punt",
"IdentifyRegion": "Identificar zona",
Expand Down Expand Up @@ -130,6 +131,12 @@
"querying": "Consultant...",
"title": "Formulari d'element"
},
"featuresearch": {
"noresults": "",
"query": "",
"select": "",
"title": ""
},
"fileselector": {
"files": "fitxers",
"placeholder": "Selecciona arxiu..."
Expand Down
7 changes: 7 additions & 0 deletions translations/cs-CZ.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"DxfExport": "DXF Export",
"Editing": "Úpravy",
"FeatureForm": "Editační formulář",
"FeatureSearch": "",
"Help": "Nápověda",
"IdentifyPoint": "Informace o bodu",
"IdentifyRegion": "Informace o oblasti",
Expand Down Expand Up @@ -130,6 +131,12 @@
"querying": "Načítání...",
"title": "Editační formulář"
},
"featuresearch": {
"noresults": "",
"query": "",
"select": "",
"title": ""
},
"fileselector": {
"files": "Soubory",
"placeholder": "Vybrat soubor..."
Expand Down
7 changes: 7 additions & 0 deletions translations/de-CH.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"DxfExport": "DXF-Export",
"Editing": "Editieren",
"FeatureForm": "Objektformular",
"FeatureSearch": "Objektsuche",
"Help": "Hilfe",
"IdentifyPoint": "Punkt abfragen",
"IdentifyRegion": "Region abfragen",
Expand Down Expand Up @@ -130,6 +131,12 @@
"querying": "Objekte werden abgefragt...",
"title": "Objektformular"
},
"featuresearch": {
"noresults": "Keine Ergebnisse",
"query": "Begriff",
"select": "Auswählen...",
"title": "Objektsuche"
},
"fileselector": {
"files": "Dateien",
"placeholder": "Datei auswählen"
Expand Down
7 changes: 7 additions & 0 deletions translations/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"DxfExport": "DXF-Export",
"Editing": "Editieren",
"FeatureForm": "Objektformular",
"FeatureSearch": "Objektsuche",
"Help": "Hilfe",
"IdentifyPoint": "Punkt abfragen",
"IdentifyRegion": "Region abfragen",
Expand Down Expand Up @@ -130,6 +131,12 @@
"querying": "Objekte werden abgefragt...",
"title": "Objektformular"
},
"featuresearch": {
"noresults": "Keine Ergebnisse",
"query": "Begriff",
"select": "Auswählen...",
"title": "Objektsuche"
},
"fileselector": {
"files": "Dateien",
"placeholder": "Datei auswählen"
Expand Down
7 changes: 7 additions & 0 deletions translations/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"DxfExport": "DXF Export",
"Editing": "Editing",
"FeatureForm": "Feature Form",
"FeatureSearch": "Feature Search",
"Help": "Help",
"IdentifyPoint": "Identify Point",
"IdentifyRegion": "Identify Region",
Expand Down Expand Up @@ -130,6 +131,12 @@
"querying": "Querying...",
"title": "Feature form"
},
"featuresearch": {
"noresults": "No results",
"query": "Query",
"select": "Select...",
"title": "Feature search"
},
"fileselector": {
"files": "files",
"placeholder": "Select file..."
Expand Down
7 changes: 7 additions & 0 deletions translations/es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"DxfExport": "Exportar DXF",
"Editing": "Editando",
"FeatureForm": "Formulario de Elemento",
"FeatureSearch": "",
"Help": "Ayuda",
"IdentifyPoint": "Identificar punto",
"IdentifyRegion": "Identificar zona",
Expand Down Expand Up @@ -130,6 +131,12 @@
"querying": "Consultando...",
"title": "Formulario de elemento"
},
"featuresearch": {
"noresults": "",
"query": "",
"select": "",
"title": ""
},
"fileselector": {
"files": "archivos",
"placeholder": "Seleccionar archivo..."
Expand Down
Loading

0 comments on commit f33407f

Please sign in to comment.