-
Notifications
You must be signed in to change notification settings - Fork 43
Proposal #5: Geocoder based on WFS spatial selectors
New widget based on geocoder spatial selectors to perform simple location searches by WFS
Alejandro Díaz (alediator)
MapStore 1.6
Choose one of: Under Discussion, In Progress, Completed, Rejected, Deferred
Let us show a simple Geocoder based on two related WFS layers:
- one for the street name
- one for the street number
The first aproach is available here.
You can configure it adding this plugin with this configuration:
{
"ptype": "gxp_spatial_selector_geocoder",
"outputTarget": "west",
"text": "Geocoder",
"layoutConfig":{
"xtype": "form",
"buttonAlign": "right",
"autoScroll":true,
"frame":true
},
"crossParameters":{
"name": {
"COD_STRADA":{
"number": "COD_STRADA"
}
}
},
"spatialSelectorsConfig":{
"name":{
"ptype": "gxp_spatial_geocoding_selector",
"showSelectionSummary": false,
"multipleSelection": false,
"searchComboOutputFormat": "json",
"wfsBaseURL": "http://geoserver.comune.genova.it/geoserver/wfs",
"geocoderTypeName": "CTC:V_ASTE_STRADALI_TOPONIMO_SUB",
"geocoderTypeRecordModel":[
{
"name":"id",
"mapping":"id"
},
{
"name":"name",
"mapping":"properties.NOMEVIA"
},
{
"name":"custom",
"mapping":"properties.COD_STRADA"
},
{
"name":"geometry",
"mapping":"geometry"
}
],
"geocoderTypeSortBy":null,
"geocoderTypeQueriableAttributes":[
"COD_STRADA", "NOMEVIA"
],
"spatialOutputCRS": "EPSG:3003",
"geocoderTypePageSize": 10,
"zoomToCurrentExtent": false
},
"number":{
"ptype": "gxp_spatial_geocoding_selector",
"showSelectionSummary": false,
"multipleSelection": false,
"searchComboOutputFormat": "json",
"wfsBaseURL": "http://geoserver.comune.genova.it/geoserver/wfs",
"geocoderTypeName": "SITGEO:CIVICI_COD_TOPON_SUB",
"geocoderTypeRecordModel":[
{
"name":"id",
"mapping":"id"
},
{
"name":"name",
"mapping":"properties.COD_STRADA"
},
{
"name":"custom",
"mapping":"properties.COD_TOPON"
},
{
"name":"geometry",
"mapping":"geometry"
}
],
"geocoderTypeSortBy":null,
"geocoderTypeQueriableAttributes":[
"COD_STRADA", "COD_TOPON"
],
"spatialOutputCRS": "EPSG:3003",
"geocoderTypePageSize": 10,
"zoomToCurrentExtent": false
}
}
}
You need to know the WFS feature model and include it on the spatial selectors and the crossParameters
.
In this case we use two WFS combo, one for the street name:
...
"name":{
"ptype": "gxp_spatial_geocoding_selector",
"showSelectionSummary": false,
"multipleSelection": false,
"searchComboOutputFormat": "json",
"wfsBaseURL": "http://geoserver.comune.genova.it/geoserver/wfs",
"geocoderTypeName": "CTC:V_ASTE_STRADALI_TOPONIMO_SUB",
"geocoderTypeRecordModel":[
{
"name":"id",
"mapping":"id"
},
{
"name":"name",
"mapping":"properties.NOMEVIA"
},
{
"name":"custom",
"mapping":"properties.COD_STRADA"
},
{
"name":"geometry",
"mapping":"geometry"
}
],
"geocoderTypeSortBy":null,
"geocoderTypeQueriableAttributes":[
"COD_STRADA", "NOMEVIA"
],
"spatialOutputCRS": "EPSG:3003",
"geocoderTypePageSize": 10,
"zoomToCurrentExtent": false
}
...
And another one for the street number:
...
"number":{
"ptype": "gxp_spatial_geocoding_selector",
"showSelectionSummary": false,
"multipleSelection": false,
"searchComboOutputFormat": "json",
"wfsBaseURL": "http://geoserver.comune.genova.it/geoserver/wfs",
"geocoderTypeName": "SITGEO:CIVICI_COD_TOPON_SUB",
"geocoderTypeRecordModel":[
{
"name":"id",
"mapping":"id"
},
{
"name":"name",
"mapping":"properties.COD_STRADA"
},
{
"name":"custom",
"mapping":"properties.COD_TOPON"
},
{
"name":"geometry",
"mapping":"geometry"
}
],
"geocoderTypeSortBy":null,
"geocoderTypeQueriableAttributes":[
"COD_STRADA", "COD_TOPON"
],
"spatialOutputCRS": "EPSG:3003",
"geocoderTypePageSize": 10,
"zoomToCurrentExtent": false
}
...
And I link the first combo with the second one the COD_STRADA
parameter.
...
"crossParameters":{
"name": {
"COD_STRADA":{
"number": "COD_STRADA"
}
}
}
...
/** api: constructor
* .. class:: Geocoder(config)
*
* Geocoder based on spatial selectors
*/
gxp.plugins.spatialselector.Geocoder = Ext.extend(gxp.plugins.Tool, {
/* ptype = gxp_spatial_selector_geocoder */
ptype : 'gxp_spatial_selector_geocoder',
/** api: config[layoutConfig]
* ``Object``
* Configuration for the output layout.
*/
/** api: config[crossParameters]
* ``Object``
* Configuration for crossParameters by selectors. When you select a result for the first combo,
* it will enable the second or more comboboxes and copy the parameter configured in the second one
*/
crossParameters:{},
/** api: config[searchBtnCls]
* ``String``
* Icon cls for the search button.
*/
searchBtnCls: "gxp-icon-find",
/** api: config[resetBtnCls]
* ``String``
* Icon cls for the search button.
*/
resetBtnCls: "cancel",
/** i18n **/
/** api: config[titleText]
* ``String``
* Title for the output (i18n).
*/
titleText: "Geocoder",
/** api: config[searchText]
* ``String``
* Search text (i18n).
*/
searchText: "Search",
/** api: config[searchTpText]
* ``String``
* Search tooltip text (i18n).
*/
searchTpText: "Search selected location and zoom in on map",
/** api: config[resetText]
* ``String``
* Reset text (i18n).
*/
resetText: "Reset",
/** api: config[resetText]
* ``String``
* Reset text (i18n).
*/
resetTpText: "Reset location search",
/** api: config[translatedKeys]
* ``String``
* Translated keys for spatial selectors (i18n).
*/
translatedKeys: {
"name": "Street",
"number": "Number"
},
/** EoF i18n **/
/** api: method[constructor]
* Init spatialSelectors .
*/
constructor : function(config) {
// default layout configuration
this.layoutConfig = {
xtype: "panel",
layout: "form",
defaults:{
layout:"fieldfet"
}
};
// Apply config
Ext.apply(this, config);
return gxp.plugins.spatialselector.SpatialSelector.superclass.constructor.call(this, arguments);
},
/** api: method[addOutput]
*/
addOutput: function() {
// initialize spatial selectors
this.spatialSelectors = {};
this.spatialSelectorsItems = [];
if(this.spatialSelectorsConfig){
for (var key in this.spatialSelectorsConfig){
var spConfig = this.spatialSelectorsConfig[key];
spConfig.target = this.target;
// Add i18n support by spatial selector
if(this.translatedKeys[key]){
spConfig["name"] = this.translatedKeys[key];
}
if(spConfig.id
&& this.target
&& this.target.tools
&& this.target.tools[spConfig.id]){
this.spatialSelectors[key] = this.target.tools[spConfig.id];
}else{
var plugin = Ext.ComponentMgr.createPlugin(spConfig);
if(this.target
&& this.target.tools){
this.target.tools[spConfig.id] = plugin;
}
this.spatialSelectors[key] = plugin;
var selectorItem = plugin.getSelectionMethodItem();
selectorItem.value = key;
this.spatialSelectorsItems.push(selectorItem);
}
}
}
// prepare layout
var layout = {};
Ext.apply(layout, this.layoutConfig);
if(!layout.title){
layout.title = this.titleText;
}
// initialize layout
layout.items = [];
if(this.spatialSelectors){
for (var key in this.spatialSelectors){
var output = this.spatialSelectors[key].addOutput();
output.on("geometrySelect", this.onGeometrySelect, {scope:this, type: key});
if(output){
if(!this.crossParameters[key]){
output.setDisabled(true);
}
layout.items.push(output);
}
}
}
layout.buttonAlign = "right";
layout.bbar = [
"->",
{
xtype : "button",
text : this.searchText,
tooltip : this.searchTpText,
iconCls: this.searchBtnCls,
scope : this,
handler : this.search
},{
xtype : "button",
text : this.resetText,
tooltip : this.resetTpText,
iconCls: this.resetBtnCls,
scope : this,
handler : this.reset
}];
var output = gxp.plugins.spatialselector.Geocoder.superclass.addOutput.call(this, layout);
return output;
},
onGeometrySelect:function(geometry){
var params = this;
var me = params.scope;
var type = params.type;
var selected = me.spatialSelectors[type].wfsComboBox.getValue();
var store = me.spatialSelectors[type].wfsComboBox.store;
var records = store.getRange();
var size = store.getCount();
var recordData = null;
for(var i = 0; i < size; i++){
var record = records[i];
if (record && record.data.name == selected) {
recordData = record;
break;
}
}
var selectedProperties = recordData.json.properties;
if(me.crossParameters[type]){
for(var parameter in me.crossParameters[type]){
var value = selectedProperties[parameter];
for(var targetCombo in me.crossParameters[type][parameter]){
// enable combo
me.spatialSelectors[targetCombo].output.setDisabled(false);
var comboBox = me.spatialSelectors[targetCombo].wfsComboBox;
// Apply filter (only one at time)
if(!comboBox.vendorParams){
comboBox.vendorParams = {};
}
var cql_filter = me.crossParameters[type][parameter][targetCombo] + " = " + value;
Ext.apply(comboBox.vendorParams,{
cql_filter: cql_filter
});
}
}
}
me.geometry = geometry;
},
/** api: method[reset]
* Search action.
*/
search: function(){
var geometry = this.geometry;
if (geometry && geometry.getBounds) {
var dataExtent = geometry.getBounds();
this.target.mapPanel.map.zoomToExtent(dataExtent, closest=false);
}
},
/** api: method[reset]
* Reset the state of the Geocoder.
*/
reset: function(){
this.geometry = null;
if(this.spatialSelectors){
for (var key in this.spatialSelectors){
this.spatialSelectors[key].wfsComboBox.reset();
this.spatialSelectors[key].reset();
if(!this.crossParameters[key]){
this.spatialSelectors[key].output.setDisabled(true);
}
}
}
}
});
The most important configurations are:
- spatialSelectorsConfig: Spatial selector plugins configurations
- crossParameters: Configuration for crossParameters by selectors. When you select a result for the first combo, it will enable the second or more comboboxes and copy the parameter configured in the second one
You can extend it as you want, for example, you can concatenate more than two WFS layers.
This section should contain feedback provided by members who may have a problem with the proposal.
The new code will be compatible with GXP and MapPanel. Should be therefore backward compatible with all versions of MapStore.
Alejandro Díaz: +1 Mauro Bartolomeoli: +1