Skip to content
This repository has been archived by the owner on May 6, 2021. It is now read-only.

Proposal #5: Geocoder based on WFS spatial selectors

mbarto edited this page Mar 5, 2014 · 2 revisions

Overview

New widget based on geocoder spatial selectors to perform simple location searches by WFS

Proposed By

Alejandro Díaz (alediator)

Assigned to Release

MapStore 1.6

State

Choose one of: Under Discussion, In Progress, Completed, Rejected, Deferred

Motivation

Let us show a simple Geocoder based on two related WFS layers:

  • one for the street name
  • one for the street number

Proposal

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"
			    	}
		    	}
		    }
...

Class/Interface Name

/** 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.

Feedback

This section should contain feedback provided by members who may have a problem with the proposal.

Backwards Compatibility

The new code will be compatible with GXP and MapPanel. Should be therefore backward compatible with all versions of MapStore.

Voting

Alejandro Díaz: +1 Mauro Bartolomeoli: +1

Clone this wiki locally