smas1 / geoext-viewer

Automatically exported from code.google.com/p/geoext-viewer
GNU General Public License v3.0
0 stars 0 forks source link

Add buffer functionality to search by feature panel. #394

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago

Add "buffer" to search by feature.

"Buffer" is a very useful (and often used) tool in gis world, it permits 
several geographic alalysis.
 Since we already have a similar feature (intersects) in search by feature panel, it would be nice to extend it by giving the user the posibility to set a distance range for the request.

This enhancement requires some changes in heron.js (at least in my approach), 
this is the code involved:

heron-with-ux.js line 2222

...
var 
spatialFilterType=options.spatialFilterType||OpenLayers.Filter.Spatial.DWITHIN; 
//changed the default from INTERSECTS to DWITHIN
var spatialFilterDistance=options.spatialFilterDistance||0;                     
                               //added a variable for distance, defaults to 0
var filter=new 
OpenLayers.Filter.Spatial({type:spatialFilterType,value:geometry,distanceUnits:'
m',distance:spatialFilterDistance}); 
//set distance and units in filter definition 
//(units are hardcoded so far). 
//When filter=intersects||contains distance is ignored 
if(geometries.length>1){
    var filters=[];geometry=new OpenLayers.Geometry.Collection();
    Ext.each(geometries,function(g){
        geometry.addComponent(g);
        filters.push(new OpenLayers.Filter.Spatial(
            {type:spatialFilterType,value:g,distanceUnits:'m',distance:spatialFilterDistance})); 
//same for chained filters
    });
    filter=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.OR,filters:filters});
}
...code...

SearchByFeaturePanel_modified.js changes 
(overrides definition in heron-with-ux.js, line 2241)

...code...

   initComponent: function () {

        this.resetButton = new Ext.Button({
            anchor: "20%",
            text: 'Reset',
            tooltip: __('Start a new search'),
            listeners: {
                click: function () {
                    this.resetForm();
                },
                scope: this
            }

        });

        this.items = [
            this.createSourceLayerCombo(),
            this.createDrawFieldSet(),
            this.createTargetLayerCombo({selectFirst: true}),
            this.createSearchTypeCombo(),
        this.createSearchDistField(),  
//added distance field
            this.createActionButtons(),
            this.createStatusPanel(),
            this.resetButton
        ];

        Heron.widgets.search.SearchByFeaturePanel.superclass.initComponent.call(this);
    },

...

    resetForm: function () {
        this.selectionLayer.removeAllFeatures();
        this.searchButton.disable();
        this.sourceLayerCombo.reset();
        this.targetLayerCombo.reset();
        this.spatialFilterType = OpenLayers.Filter.Spatial.INTERSECTS;
        this.drawFieldSet.hide();
        this.deactivateDrawControl();
        this.selectionStatusField.hide();
        this.targetLayerCombo.hide();
        this.searchTypeCombo.hide();
    this.searchDistField.hide();  
//added
        this.actionButtons.hide();
        this.updateStatusPanel(__('Select a source Layer and then draw to select objects from that layer. <br/>Then select a target Layer to search in using the geometries of the selected objects.'));
        this.fireEvent('searchreset');
    },

    ...

    createDrawFieldSet: function () {

        this.selectionStatusField = new Heron.widgets.HTMLPanel({
            html: __('No objects selected'),
            preventBodyReset: true,
            bodyCfg: {
                style: {
                    padding: '2px',
                    border: '0px'
                }
            },
            style: {
                marginTop: '2px',
                marginBottom: '2px',
                fontFamily: 'Verdana, Arial, Helvetica, sans-serif',
                fontSize: '11px',
                color: '#0000C0'
            }
        });

        return this.drawFieldSet = new Ext.form.FieldSet(
                {
                    xtype: "fieldset",
                    title: null,
                    anchor: "100%",
                    items: [
                        this.createDrawToolPanel({
                                    style: {
                                        marginTop: '12px',
                                        marginBottom: '12px'
                                    },
                                    activateControl: false}
                        ),

                        this.selectionStatusField
                    ]
                }
        );
    },
    createSearchDistField: function () {                                         //added field definition
       return this.searchDistField = new Ext.form.NumberField({
            fieldLabel: __('Distance of Search'),
            name: 'basic',
            value: 0,
            minValue: 0,
            maxValue: 9999,
            // all of your config options
            enableKeyEvents: true,
            listeners: {
                keyup: function (numberfield, ev) {
                    this.spatialFilterDistance = numberfield.getValue();
                },
                scope: this
            }
        });
    },

    createSearchTypeCombo: function () {
        var store = new Ext.data.ArrayStore({
            fields: ['name', 'value'],
            data: [
                ['INTERSECTS (default)', OpenLayers.Filter.Spatial.INTERSECTS],
        ['BUFFER', OpenLayers.Filter.Spatial.DWITHIN],                                //replaced WITHIN with DWITHIN 
//and changed denomination for BUFFER
        ['CONTAINS', OpenLayers.Filter.Spatial.CONTAINS]
            ]
        });
        return this.searchTypeCombo = new Ext.form.ComboBox({
            mode: 'local',
//            anchor: "100%",
            listWidth: 160,
            value: store.getAt(0).get("name"),
            fieldLabel: __('Type of Search'),
            store: store,
            displayField: 'name',
            valueField: 'value',
            forceSelection: true,
            triggerAction: 'all',
            editable: false,
            // all of your config options
            listeners: {
                select: function (cb, record) {
                    this.spatialFilterType = record.data['value'];
                    if(record.data['name']=='BUFFER'){
                        this.searchDistField.show();                      
//show distance field only when BUFFER is selected
                        }else{
                        this.searchDistField.hide();
                        }
                },
                scope: this
            }
        });
    },
...

   /** api: method[searchFromFeatures]
     *
     *  Issue spatial search via WFS.
     */
    searchFromFeatures: function () {
        var geometries = [];
        var features = this.selectionLayer.features;
        for (var i = 0; i < features.length; i++) {
            geometries.push(features[i].geometry);
        }
        this.searchButton.disable();
        this.cancelButton.enable();
        if (!this.search(geometries, {spatialFilterType: this.spatialFilterType, spatialFilterDistance: this.spatialFilterDistance, targetLayer: this.targetLayer,   
//added spatialFilterDistance to search parameters
            projection: this.selectionLayer.projection, units: this.selectionLayer.units})) {
            this.selectionLayer.removeAllFeatures();
        }
    }

...code...

Original issue reported on code.google.com by 486.m...@gmail.com on 23 Jul 2014 at 3:38

Attachments:

GoogleCodeExporter commented 9 years ago
I reviewed the proposal. In general it looks good and very useful. Couple of 
remarks/questions:

- the first set of changes are in SpatialSearchPanel.js baseclass search() 
function
- changing the default from INTERSECTS to DWITHIN is strictly IMO not related 
to BUFFER search. This may affect existing apps that use the SearchByDrawPanel 
(which uses default) unexpectedly. Solution is to always set the 
spatialFilterType but this should be a separate ticket/issue. BUFFER will still 
work I guess without the change of default.
- configure units in SearchByFeaturePanel config  instead of hardcoded 'm' 
(will be default)
- I would still like to keep WITHIN so we will have 4 filter types: INTERSECTS, 
WITHIN, BUFFER and CONTAINS with WITHIN as before.
-perhaps a more user-friendly name for BUFFER like WITHIN DISTANCE unless 
BUFFER is the common GIS-folks term (I have no ArcGIS background!).

Let's proceed. This type of functions are the icing on the cake, making Heron 
very useful as a GIS tool!

Original comment by jus...@gmail.com on 5 Aug 2014 at 10:48

GoogleCodeExporter commented 9 years ago
Great! The buffer is really useful for my users already, and its functionality 
is something that was missing from almost all geoext-based (and open source) 
viewers (although it was included in early argis products as arcims viewer of 
2004, ouch).

Some comments:

- the first set of changes are in SpatialSearchPanel.js baseclass search() 
function
Thanks, i missed that, and now i can switch to an unmodified heron.js file

- changing the default from INTERSECTS to DWITHIN is strictly IMO not related 
to BUFFER search. This may affect existing apps that use the SearchByDrawPanel 
(which uses default) unexpectedly. Solution is to always set the 
spatialFilterType but this should be a separate ticket/issue. BUFFER will still 
work I guess without the change of default.
You´re right. I set the default for practical reasons, principally about 
sending always the distance parameter. The tests i made after that show me that 
at least in geoserver clients distance is ignored when inserted in the request 
and the filter is othen than dwithin, and is also filtered in the request made 
by heron.

- configure units in SearchByFeaturePanel config  instead of hardcoded 'm' 
(will be default)
I agree. I hardcoded the units because of lack of programming skills!(more or 
less like my english skills)I didn´t figure how to interact with panel config.

- I would still like to keep WITHIN so we will have 4 filter types: INTERSECTS, 
WITHIN, BUFFER and CONTAINS with WITHIN as before.
I think i don´t understand how WITHIN works. Isn´t like DWITHIN with 
distance=0?

-perhaps a more user-friendly name for BUFFER like WITHIN DISTANCE unless 
BUFFER is the common GIS-folks term (I have no ArcGIS background!).
I agree. I used BUFFER because my users are used to the denomination. BTW, 
although the names are pretty self-explanatory, i would recommend adding them 
to the language file so we can have a translation. I imagine some of the 
spanish users wandering what "WITHIN DISTANCE" means.

Original comment by 486.m...@gmail.com on 5 Aug 2014 at 1:14

GoogleCodeExporter commented 9 years ago
Hi I integrated your code, made some what I think improvements. You can try the 
example at
http://lib.heron-mc.org/heron/latest/examples/multisearchcenternl ('meters' 
projection)

- units: made configurable, default is 'meter' (this is the standard, not 'm'). 
HOWEVER, there is an issue with GeoServer and DWITHIN that it ignores the units 
attr and will always take the layer units: 
http://osgeo-org.1560.x6.nabble.com/DWITHIN-is-picking-up-too-many-points-td3791
488.html. See the problem in WGS84 example: 
http://lib.heron-mc.org/heron/latest/examples/multisearchcenter taking deegrees.
- added i18N for strings like BUFFER etc
- only add distance and units to WFS Filter for DWITHIN

- not implemented: a visualization of extending buffers in the source 
geometries (challenging)

I am still wondering if DWITHIN is really the same as BUFFER (in the ESRI 
sense). For a POINT ok, but not sure how this behaves with LINESTRINGS and 
POLYGONS.

Hopefully others find this feature useful and could test.

Original comment by jus...@gmail.com on 6 Aug 2014 at 3:44

GoogleCodeExporter commented 9 years ago
The GeoServer 'DWITHIN units"  issue is here: 
http://jira.codehaus.org/browse/GEOS-937

Did some more thinking about Buffer. Though the current setup is useful, I 
don't think it deserves to be called buffer (yet). For several reasons I think 
it would be better to regard Buffer as an intermediate step that will generate 
a new Layer, whose geometries can then be used for a WFS search, even using 
either CONTAINS, WITHIN or INTERSECTS with the Buffer. Also the Buffer could 
then be used also within the 'Search by Draw",maybe an even more common Buffer 
use case. 

To implement, Buffer creation could/should be done client-side. This has the 
following advantages:

- no server-side issues for non-metric projections
- easier to visualize
- easier to reuse

The library JSTS, a JavaScript port of the well-established Java JTS library as 
used in GeoServer via GeoTools, seems a good candidate: 
https://github.com/bjornharrtell/jsts with a Buffer example:
http://bjornharrtell.github.io/jsts/examples/buffer.html

I propose that if the current DWITHIN implementation satisfies your use cases 
we may close this issue. I can then open a new issue for a 
general/visual/client-side Buffer implementation for both "search by draw" and 
"search by feature".  Anyone willing to fund such an advanced function?

Original comment by jus...@gmail.com on 7 Aug 2014 at 10:17

GoogleCodeExporter commented 9 years ago
I agree with closing the issue, and also about the denomination (distance 
within or its tranlation seems more appropiate).
Thank you for the good work!

Original comment by 486.m...@gmail.com on 7 Aug 2014 at 1:02

GoogleCodeExporter commented 9 years ago
Ok, closing this issue and opening issue 398 for geneirc Buffer implementation.

Original comment by jus...@gmail.com on 7 Aug 2014 at 4:10