Skip to content

Commit

Permalink
Initial multiservice support for search
Browse files Browse the repository at this point in the history
- Changed internal representation from nominatim to GeoJson
- Fixed geosolutions-it#1507 Now Openlayers uses default style too
- Fixes geosolutions-it#1505 Now the markers are correctly displaced
- The selector for layers with marker generate marker layers only inside the selector (still to remove loading from flat layer to get better performances)
- Initial support for WFS as external services
- Moved businness logic from UI into a new epic
  • Loading branch information
offtherailz committed Feb 24, 2017
1 parent ba1ed63 commit bd3cf3a
Show file tree
Hide file tree
Showing 20 changed files with 471 additions and 100 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
"turf-bbox": "3.0.10",
"turf-buffer": "3.0.10",
"turf-intersect": "3.0.10",
"turf-point-on-surface": "3.0.10",
"turf-union": "3.0.10",
"url": "0.10.3",
"w3c-schemas": "1.3.1",
Expand Down
2 changes: 1 addition & 1 deletion tests.webpack.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
var context = require.context('./web', true, /(-test\.jsx?)|(-test-chrome\.jsx?)$/);
var context = require.context('./web/client/epics', true, /(-test\.jsx?)|(-test-chrome\.jsx?)$/);
context.keys().forEach(context);
module.exports = context;
10 changes: 1 addition & 9 deletions web/client/actions/__tests__/layers-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ describe('Test correctness of the layers actions', () => {
expect(action.type).toBe(UPDATE_SETTINGS);
expect(action.options).toEqual({opacity: 0.5, size: 500});
});
it('get layer capabilities', (done) => {
it('get layer capabilities', () => {
const layer = {
id: "TEST_ID",
name: 'testworkspace:testlayer',
Expand All @@ -180,13 +180,5 @@ describe('Test correctness of the layers actions', () => {
};
const actionCall = getLayerCapabilities(layer);
expect(actionCall).toExist();
actionCall((action)=> {
expect(action).toExist();
expect(action.options).toExist();
expect(action.type === UPDATE_NODE);
if (action.options.capabilities) {
done();
}
});
});
});
11 changes: 10 additions & 1 deletion web/client/actions/__tests__/search-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ var {
TEXT_SEARCH_LOADING,
TEXT_SEARCH_ERROR,
TEXT_SEARCH_STARTED,
TEXT_SEARCH_ITEM_SELECTED,
searchResultLoaded,
searchTextLoading,
searchResultError,
textSearch
textSearch,
selectSearchItem
} = require('../search');

describe('Test correctness of the search actions', () => {
Expand Down Expand Up @@ -49,5 +51,12 @@ describe('Test correctness of the search actions', () => {
expect(retval2.results).toEqual(testVal);
expect(retval2.append).toBe(true);
});
it('serch item selected', () => {
const retval = selectSearchItem("A", "B");
expect(retval).toExist();
expect(retval.type).toBe(TEXT_SEARCH_ITEM_SELECTED);
expect(retval.item).toEqual("A");
expect(retval.mapConfig).toBe("B");
});

});
25 changes: 20 additions & 5 deletions web/client/actions/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ const TEXT_SEARCH_TEXT_CHANGE = 'TEXT_SEARCH_TEXT_CHANGE';
const TEXT_SEARCH_LOADING = 'TEXT_SEARCH_LOADING';
const TEXT_SEARCH_ERROR = 'TEXT_SEARCH_ERROR';

function searchResultLoaded(results, append=false) {
const TEXT_SEARCH_ITEM_SELECTED = 'TEXT_SEARCH_ITEM_SELECTED';

function searchResultLoaded(results, append=false, services) {
return {
type: TEXT_SEARCH_RESULTS_LOADED,
results: results,
append: append
append: append,
services
};
}

Expand Down Expand Up @@ -64,11 +67,21 @@ function addMarker(itemPosition) {
};
}

function textSearch(searchText) {
function textSearch(searchText, {services = null} = {}) {
return {
type: TEXT_SEARCH_STARTED,
searchText
searchText,
services
};
}

function selectSearchItem(item, mapConfig) {
return {
type: TEXT_SEARCH_ITEM_SELECTED,
item,
mapConfig
};

}


Expand All @@ -82,12 +95,14 @@ module.exports = {
TEXT_SEARCH_RESET,
TEXT_SEARCH_ADD_MARKER,
TEXT_SEARCH_TEXT_CHANGE,
TEXT_SEARCH_ITEM_SELECTED,
searchTextLoading,
searchResultError,
searchResultLoaded,
textSearch,
resultsPurge,
resetSearch,
addMarker,
searchTextChanged
searchTextChanged,
selectSearchItem
};
8 changes: 4 additions & 4 deletions web/client/api/Nominatim.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ var axios = require('../libs/ajax');
const urlUtil = require('url');
const assign = require('object-assign');
const DEFAULT_URL = 'nominatim.openstreetmap.org';
const DEFAUTL_REVERSE_URL = 'nominatim.openstreetmap.org/reverse';
const DEFAULT_REVERSE_URL = 'nominatim.openstreetmap.org/reverse';
const defaultOptions = {
format: 'json',
bounded: 0,
addressdetails: 1
polygon_geojson: 1
};
/**
* API for local config
*/
const Api = {
geocode: function(text, options) {
var params = assign({q: text}, options || {}, defaultOptions);
var params = assign({q: text}, defaultOptions, options || {});
var url = urlUtil.format({
protocol: window.location.protocol,
host: DEFAULT_URL,
Expand All @@ -32,7 +32,7 @@ const Api = {
const params = assign({lat: coords.lat, lon: coords.lng}, options || {}, defaultOptions);
const url = urlUtil.format({
protocol: window.location.protocol,
host: DEFAUTL_REVERSE_URL,
host: DEFAULT_REVERSE_URL,
query: params
});
return axios.get(url);
Expand Down
28 changes: 16 additions & 12 deletions web/client/api/searchText.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
*/
const WFS = require('./WFS');
const assign = require('object-assign');


const GeoCodeUtils = require('../utils/GeoCodeUtils');
/*
const toNominatim = (fc) =>
fc.features && fc.features.map( (f) => ({
boundingbox: f.properties.bbox,
Expand All @@ -17,18 +17,22 @@ const toNominatim = (fc) =>
display_name: `${f.properties.STATE_NAME} (${f.properties.STATE_ABBR})`
}));

*/

module.exports = {
nominatim: (searchText) => require('./Nominatim').geocode(searchText).then( res => res.data),
nominatim: (searchText, {options = null} = {}) =>
require('./Nominatim')
.geocode(searchText, options)
.then( res => GeoCodeUtils.nominatimToGeoJson(res.data)),
wfs: (searchText, {url, typeName, queriableAttributes, outputFormat="application/json", predicate ="ILIKE", ...params }) => {
return WFS.getFeatureSimple(url,
assign({
maxFeatures: 10,
startIndex: 0,
typeName,
outputFormat,
cql_filter: queriableAttributes.map( attr => `${attr} ${predicate} '%${searchText}%'`).join(' OR ')
}, params)).then( response => toNominatim(response ));
return WFS
.getFeatureSimple(url, assign({
maxFeatures: 10,
startIndex: 0,
typeName,
outputFormat,
cql_filter: queriableAttributes.map( attr => `${attr} ${predicate} '%${searchText}%'`).join(' OR ')
}, params))
.then( response => response.features );
}
};
9 changes: 7 additions & 2 deletions web/client/components/map/leaflet/Feature.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,16 @@ var getPointLayer = function(pointToLayer, geojson, latlng, options) {
latlng,
{
icon: L.icon({
iconUrl: options.style.iconUrl
iconUrl: options.style.iconUrl,
iconSize: options.style.iconSize,
shadowSize: options.style.shadowSize,
iconAnchor: options.style.iconAnchor,
shadowAnchor: options.style.shadowAnchor,
popupAnchor: options.style.popupAnchor
})
});
}
return new L.Marker(latlng);
return L.marker(latlng);
};

var geometryToLayer = function(geojson, options) {
Expand Down
54 changes: 43 additions & 11 deletions web/client/components/map/openlayers/plugins/VectorLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,25 +142,57 @@ Layers.registerType('vector', {
};
}

if (options.style.iconUrl) {
style = {
image: new ol.style.Icon(({
anchor: [0.5, 1],
anchorXUnits: 'fraction',
anchorYUnits: 'fraction',
src: options.style.iconUrl
}))
if (options.style.iconUrl ) {
let markerStyle = [new ol.style.Style({
image: new ol.style.Icon(({
anchor: options.iconAnchor || [0.5, 1],
anchorXUnits: options.iconAnchor ? 'pixels' : 'fraction',
anchorYUnits: options.iconAnchor ? 'pixels' : 'fraction',
src: options.style.iconUrl
}))
})];
if (options.shadowUrl) {
markerStyle = [new ol.style.Style({
image: new ol.style.Icon(({
anchor: [12, 41],
anchorXUnits: 'pixels',
anchorYUnits: 'pixels',
src: options.style.shadowUrl || markerShadow
}))
}), markerStyle];
}
style = (feature) => {
const type = feature.getGeometry().getType();
switch (type) {
case "Point":
case "MultiPoint":
return markerStyle;
default:
return styleFunction(feature);
}
};
} else {
style = new ol.style.Style(style);
}

style = new ol.style.Style(style);
}

return new ol.layer.Vector({
msId: options.id,
source: source,
zIndex: options.zIndex,
style: (options.styleName && !options.overrideOLStyle) ? () => {return defaultStyles[options.styleName]; } : style || styleFunction
style: (options.styleName && !options.overrideOLStyle) ? (feature) => {
if (options.styleName === "marker") {
const type = feature.getGeometry().getType();
switch (type) {
case "Point":
case "MultiPoint":
return defaultStyles.marker;
default:
break;
}
}
return defaultStyles[options.styleName];
} : style || styleFunction
});
},
update: (layer, newOptions, oldOptions) => {
Expand Down
46 changes: 46 additions & 0 deletions web/client/components/mapcontrols/search/SearchResult.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* 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 {get} = require('lodash');


let SearchResult = React.createClass({
propTypes: {
item: React.PropTypes.object,
displayName: React.PropTypes.string,
idField: React.PropTypes.string,
icon: React.PropTypes.string,
onItemClick: React.PropTypes.func
},
getDefaultProps() {
return {
displayName: "properties.display_name",
idField: "id",
icon: "properties.icon"
};
},
onClick() {
let item = this.props.item;
this.props.onItemClick(item);
},
render() {
if (this.props.item === undefined) {
return null;
}
let item = this.props.item;
return (
<div key={item.osm_id} className="search-result NominatimResult" onClick={this.onClick}>
<div className="icon"> <img src={item.icon} /></div>
{get(item, this.props.displayName) }
</div>
);
}
});

module.exports = SearchResult;
Loading

0 comments on commit bd3cf3a

Please sign in to comment.