UNPKG

@geoext/geoext

Version:

GeoExt is Open Source and enables building desktop-like GIS applications through the web. It is a JavaScript framework that combines the GIS functionality of OpenLayers with the user interface savvy of the ExtJS library provided by Sencha.

809 lines (767 loc) 26 kB
/* Copyright (c) 2015-present The Open Source Geospatial Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /** * A utility class for converting ExtJS filters to OGC compliant filters * * @class GeoExt.util.OGCFilter */ Ext.define('GeoExt.util.OGCFilter', { statics: { /** * The WFS 1.0.0 GetFeature XML body template */ wfs100GetFeatureXmlTpl: '<wfs:GetFeature service="WFS" version="1.0.0"' + ' outputFormat="JSON"' + ' xmlns:wfs="http://www.opengis.net/wfs"' + ' xmlns="http://www.opengis.net/ogc"' + ' xmlns:gml="http://www.opengis.net/gml"' + ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' + ' xsi:schemaLocation="http://www.opengis.net/wfs' + ' http://schemas.opengis.net/wfs/1.0.0/WFS-basic.xsd">' + '<wfs:Query typeName="{0}">{1}' + '</wfs:Query>' + '</wfs:GetFeature>', /** * The WFS 1.1.0 GetFeature XML body template */ wfs110GetFeatureXmlTpl: '<wfs:GetFeature service="WFS" version="1.1.0"' + ' outputFormat="JSON"' + ' xmlns:wfs="http://www.opengis.net/wfs"' + ' xmlns="http://www.opengis.net/ogc"' + ' xmlns:gml="http://www.opengis.net/gml"' + ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' + ' xsi:schemaLocation="http://www.opengis.net/wfs' + ' http://schemas.opengis.net/wfs/1.0.0/WFS-basic.xsd">' + '<wfs:Query typeName="{0}">{1}' + '</wfs:Query>' + '</wfs:GetFeature>', /** * The WFS 2.0.0 GetFeature XML body template */ wfs200GetFeatureXmlTpl: '<wfs:GetFeature service="WFS" version="2.0.0" ' + 'xmlns:wfs="http://www.opengis.net/wfs/2.0" ' + 'xmlns:fes="http://www.opengis.net/fes/2.0" ' + 'xmlns:gml="http://www.opengis.net/gml/3.2" ' + 'xmlns:sf="http://www.openplans.org/spearfish" ' + 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' + 'xsi:schemaLocation="http://www.opengis.net/wfs/2.0 ' + 'http://schemas.opengis.net/wfs/2.0/wfs.xsd ' + 'http://www.opengis.net/gml/3.2 ' + 'http://schemas.opengis.net/gml/3.2.1/gml.xsd">' + '<wfs:Query typeName="{0}">{1}' + '</wfs:Query>' + '</wfs:GetFeature>', /** * The template for spatial filters used in WFS 1.x.0 queries */ spatialFilterWfs1xXmlTpl: '<{0}>' + '<PropertyName>{1}</PropertyName>' + '{2}' + '</{0}>', /** * The template for spatial filters used in WFS 2.0.0 queries */ spatialFilterWfs2xXmlTpl: '<fes:{0}>' + '<fes:ValueReference>{1}</fes:ValueReference>' + '{2}' + '</fes:{0}>', /** * The template for spatial bbox filters used in WFS 1.x.0 queries */ spatialFilterBBoxTpl: '<BBOX>' + ' <PropertyName>{0}</PropertyName>' + ' <gml:Envelope' + ' xmlns:gml="http://www.opengis.net/gml" srsName="{1}">' + ' <gml:lowerCorner>{2} {3}</gml:lowerCorner>' + ' <gml:upperCorner>{4} {5}</gml:upperCorner>' + ' </gml:Envelope>' + '</BBOX>', /** * Template string for GML 3.2.1 polygon */ gml32PolygonTpl: '<gml:Polygon gml:id="P1" ' + 'srsName="urn:ogc:def:crs:{0}" srsDimension="2">' + '<gml:exterior>' + '<gml:LinearRing>' + '<gml:posList>{1}</gml:posList>' + '</gml:LinearRing>' + '</gml:exterior>' + '</gml:Polygon>', /** * Template string for GML 3.2.1 linestring */ gml32LineStringTpl: '<gml:LineString gml:id="L1" ' + 'srsName="urn:ogc:def:crs:{0}" srsDimension="2">' + '<gml:posList>{1}</gml:posList>' + '</gml:LineString>', /** * Template string for GML 3.2.1 point */ gml32PointTpl: '<gml:Point gml:id="Pt1" ' + 'srsName="urn:ogc:def:crs:{0}" srsDimension="2">' + '<gml:pos>{1}</gml:pos>' + '</gml:Point>', /** * The start element for a FE filter instance in version 2.0 * as string value */ filter20StartElementStr: '<fes:Filter ' + 'xsi:schemaLocation="http://www.opengis.net/fes/2.0 ' + 'http://schemas.opengis.net/filter/2.0/filterAll.xsd ' + 'http://www.opengis.net/gml/3.2 ' + 'http://schemas.opengis.net/gml/3.2.1/gml.xsd" ' + 'xmlns:fes="http://www.opengis.net/fes/2.0" ' + 'xmlns:gml="http://www.opengis.net/gml/3.2" ' + 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">', /** * The list of supported topological and spatial filter operators */ topologicalOrSpatialFilterOperators: [ 'intersect', 'within', 'contains', 'equals', 'disjoint', 'crosses', 'touches', 'overlaps', 'bbox', ], /** * Given an array of ExtJS grid-filters, this method will return an OGC * compliant filter which can be used for WMS requests * @param {Array<Ext.util.Filter>} filters array containing all * `Ext.util.Filter` that should be converted * @param {string} combinator The combinator used for combining multiple * filters. Can be 'and' or 'or' * @return {string} The OGC Filter XML */ getOgcWmsFilterFromExtJsFilter: function (filters, combinator) { return GeoExt.util.OGCFilter.getOgcFilterFromExtJsFilter( filters, 'wms', combinator, ); }, /** * Given an array of ExtJS grid-filters, this method will return an OGC * compliant filter which can be used for WFS requests * @param {Array<Ext.util.Filter>} filters array containing all * `Ext.util.Filter` that should be converted * @param {string} combinator The combinator used for combining multiple * filters. Can be 'and' or 'or' * @param {string} wfsVersion The WFS version to use, either `1.0.0`, * `1.1.0` or `2.0.0` * @return {string} The OGC Filter XML */ getOgcWfsFilterFromExtJsFilter: function (filters, combinator, wfsVersion) { return GeoExt.util.OGCFilter.getOgcFilterFromExtJsFilter( filters, 'wfs', combinator, wfsVersion, ); }, /** * Given an ExtJS grid-filter, this method will return an OGC compliant * filter which can be used for WMS or WFS queries * @param {Array<Ext.util.Filter>} filters array containing all * `Ext.util.Filter` that should be converted * @param {string} type The OGC type we will be using, can be * `wms` or `wfs` * @param {string} combinator The combinator used for combining multiple * filters. Can be 'and' or 'or' * @param {string} wfsVersion The WFS version to use, either `1.0.0`, * `1.1.0` or `2.0.0` * @return {string} The OGC Filter as XML String */ getOgcFilterFromExtJsFilter: function ( filters, type, combinator, wfsVersion, ) { if (!Ext.isDefined(filters) || !Ext.isArray(filters)) { Ext.Logger.error( 'Invalid filter argument given to ' + 'GeoExt.util.OGCFilter. You need to pass an array of ' + '"Ext.util.Filter"', ); return; } if (Ext.isEmpty(filters)) { return null; } let omitNamespaces = false; // filters for WMS layers need to omit the namespaces if (!Ext.isEmpty(type) && type.toLowerCase() === 'wms') { omitNamespaces = true; } const ogcFilters = []; const ogcUtil = GeoExt.util.OGCFilter; let filterBody; Ext.each(filters, function (filter) { filterBody = ogcUtil.getOgcFilterBodyFromExtJsFilterObject( filter, wfsVersion, ); if (filterBody) { ogcFilters.push(filterBody); } }); return ogcUtil.combineFilters( ogcFilters, combinator, omitNamespaces, wfsVersion, ); }, /** * Converts given ExtJS grid-filter to an OGC compliant filter * body content. * @param {Ext.util.Filter} filter Instance of * `Ext.util.Filter` which should be converted to OGC filter * @param {string} wfsVersion The WFS version to use, either `1.0.0`, * `1.1.0` or `2.0.0` * @return {string} The OGC Filter body as XML String */ getOgcFilterBodyFromExtJsFilterObject: function (filter, wfsVersion) { if (!Ext.isDefined(filter)) { Ext.Logger.error( 'Invalid filter argument given to ' + 'GeoExt.util.OGCFilter. You need to pass an instance of ' + '"Ext.util.Filter"', ); return; } const property = filter.getProperty(); const operator = filter.getOperator(); let value = filter.getValue(); let srsName; if (filter.type === 'spatial') { srsName = filter.srsName; } if ( Ext.isEmpty(property) || Ext.isEmpty(operator) || Ext.isEmpty(value) ) { Ext.Logger.warn( 'Skipping a filter as some values ' + 'seem to be undefined', ); return; } if (filter.isDateValue) { if (filter.getDateFormat) { value = Ext.Date.format(filter.getValue(), filter.getDateFormat()); } else { value = Ext.Date.format(filter.getValue(), 'Y-m-d'); } } return GeoExt.util.OGCFilter.getOgcFilter( property, operator, value, wfsVersion, srsName, ); }, /** * Returns a GetFeature XML body containing the filters * which can be used to directly request the features * @param {Array<Ext.util.Filter>} filters array containing all * `Ext.util.Filter` that should be converted * @param {string} combinator The combinator used for combining multiple * filters. Can be 'and' or 'or' * @param {string} wfsVersion The WFS version to use, either `1.0.0`, * `1.1.0` or `2.0.0` * @param {string} typeName The featuretype name to be used * @return {string} the GetFeature XML body as string */ buildWfsGetFeatureWithFilter: function ( filters, combinator, wfsVersion, typeName, ) { const filter = GeoExt.util.OGCFilter.getOgcWfsFilterFromExtJsFilter( filters, combinator, wfsVersion, ); let tpl = GeoExt.util.OGCFilter.wfs100GetFeatureXmlTpl; if (wfsVersion && wfsVersion === '1.1.0') { tpl = GeoExt.util.OGCFilter.wfs110GetFeatureXmlTpl; } else if (wfsVersion && wfsVersion === '2.0.0') { tpl = GeoExt.util.OGCFilter.wfs200GetFeatureXmlTpl; } return Ext.String.format(tpl, typeName, filter); }, /** * Returns an OGC filter for the given parameters. * @param {string} property The property to filter on * @param {string} operator The operator to use * @param {*} value The value for the filter * @param {string} wfsVersion The WFS version to use, either `1.0.0`, * `1.1.0` or `2.0.0` * @param {string} srsName The code for the projection * @return {string} The OGC filter. */ getOgcFilter: function (property, operator, value, wfsVersion, srsName) { if ( Ext.isEmpty(property) || Ext.isEmpty(operator) || Ext.isEmpty(value) ) { Ext.Logger.error( 'Invalid argument given to method ' + '`getOgcFilter`. You need to supply property, ' + 'operator and value.', ); return; } let ogcFilterType; let closingTag; let propName = 'PropertyName'; const isWfs20 = !Ext.isEmpty(wfsVersion) && wfsVersion === '2.0.0'; if (isWfs20) { propName = 'fes:ValueReference'; } // always replace surrounding quotes if (!(value instanceof ol.geom.Geometry)) { value = value.toString().replace(/(^['])/g, ''); value = value.toString().replace(/([']$)/g, ''); } const wfsPrefix = isWfs20 ? 'fes:' : ''; switch (operator) { case '==': case '=': case 'eq': ogcFilterType = wfsPrefix + 'PropertyIsEqualTo'; break; case '!==': case '!=': case 'ne': ogcFilterType = wfsPrefix + 'PropertyIsNotEqualTo'; break; case 'lt': case '<': ogcFilterType = wfsPrefix + 'PropertyIsLessThan'; break; case 'lte': case '<=': ogcFilterType = wfsPrefix + 'PropertyIsLessThanOrEqualTo'; break; case 'gt': case '>': ogcFilterType = wfsPrefix + 'PropertyIsGreaterThan'; break; case 'gte': case '>=': ogcFilterType = wfsPrefix + 'PropertyIsGreaterThanOrEqualTo'; break; case 'like': value = '*' + value + '*'; const likeFilterTpl = '<{0}PropertyIsLike wildCard="*" singleChar="."' + ' escape="!" matchCase="false">' + '<' + propName + '>' + property + '</' + propName + '>' + '<{0}Literal>' + value + '</{0}Literal>' + '</{0}PropertyIsLike>'; return Ext.String.format(likeFilterTpl, wfsPrefix); case 'in': ogcFilterType = wfsPrefix + 'Or'; let values = value; if (!Ext.isArray(value)) { // cleanup brackets and quotes value = value.replace(/([()'])/g, ''); values = value.split(','); } let filters = ''; Ext.each(values || value, function (val) { filters += '<' + wfsPrefix + 'PropertyIsEqualTo>' + '<' + propName + '>' + property + '</' + propName + '>' + '<' + wfsPrefix + 'Literal>' + val + '</' + wfsPrefix + 'Literal>' + '</' + wfsPrefix + 'PropertyIsEqualTo>'; }); ogcFilterType = '<' + ogcFilterType + '>'; let inFilter; closingTag = Ext.String.insert(ogcFilterType, '/', 1); // only use an Or filter when there are multiple values if (values.length > 1) { inFilter = ogcFilterType + filters + closingTag; } else { inFilter = filters; } return inFilter; case 'intersect': case 'within': case 'contains': case 'equals': case 'disjoint': case 'crosses': case 'touches': case 'overlaps': switch (operator) { case 'equals': ogcFilterType = 'Equals'; break; case 'contains': ogcFilterType = 'Contains'; break; case 'within': ogcFilterType = 'Within'; break; case 'disjoint': ogcFilterType = 'Disjoint'; break; case 'touches': ogcFilterType = 'Touches'; break; case 'crosses': ogcFilterType = 'Crosses'; break; case 'overlaps': ogcFilterType = 'Overlaps'; break; case 'intersect': ogcFilterType = 'Intersects'; break; default: Ext.Logger.warn( 'Method `getOgcFilter` could not ' + 'handle the given topological operator: ' + operator, ); return; } const gmlElement = GeoExt.util.OGCFilter.getGmlElementForGeometry( value, srsName, wfsVersion, ); const spatialTpl = wfsVersion !== '2.0.0' ? GeoExt.util.OGCFilter.spatialFilterWfs1xXmlTpl : GeoExt.util.OGCFilter.spatialFilterWfs2xXmlTpl; return Ext.String.format( spatialTpl, ogcFilterType, property, gmlElement, ); case 'bbox': value = value.getExtent(); const llx = value[0]; const lly = value[1]; const urx = value[2]; const ury = value[3]; return Ext.String.format( GeoExt.util.OGCFilter.spatialFilterBBoxTpl, property, srsName, llx, lly, urx, ury, ); default: Ext.Logger.warn( 'Method `getOgcFilter` could not ' + 'handle the given operator: ' + operator, ); return; } ogcFilterType = '<' + ogcFilterType + '>'; closingTag = Ext.String.insert(ogcFilterType, '/', 1); const literalStr = isWfs20 ? '<fes:Literal>{2}</fes:Literal>' : '<Literal>{2}</Literal>'; const tpl = '' + '{0}' + '<' + propName + '>{1}</' + propName + '>' + literalStr + '{3}'; const filter = Ext.String.format( tpl, ogcFilterType, property, value, closingTag, ); return filter; }, /** * Returns a serialized geometry in GML3 format * @param {ol.geometry.Geometry} geometry The geometry to serialize * @param {string} srsName The epsg code to use to serialization * @param {string} wfsVersion The WFS version to use (WFS 2.0.0 * requires gml prefix for geometries) * @return {string} The serialized geometry in GML3 format */ getGmlElementForGeometry: function (geometry, srsName, wfsVersion) { if (wfsVersion === '2.0.0') { // supported geometries: Point, LineString and Polygon // in case of multigeometries, the first one is used. const geometryType = geometry.getType(); const staticMe = GeoExt.util.OGCFilter; const isMulti = geometryType.indexOf('Multi') > -1; switch (geometryType) { case 'Polygon': case 'MultiPolygon': let coordsPoly = geometry.getCoordinates()[0]; if (isMulti) { coordsPoly = coordsPoly[0]; } return Ext.String.format( staticMe.gml32PolygonTpl, srsName, staticMe.flattenCoordinates(coordsPoly), ); case 'LineString': case 'MultiLineString': let coordsLine = geometry.getCoordinates(); if (isMulti) { coordsLine = coordsLine[0]; } return Ext.String.format( staticMe.gml32LineStringTpl, srsName, staticMe.flattenCoordinates(coordsLine), ); case 'Point': case 'MultiPoint': let coordsPt = geometry.getCoordinates(); if (isMulti) { coordsPt = coordsPt[0]; } return Ext.String.format( staticMe.gml32PointTpl, srsName, staticMe.flattenCoordinates(coordsPt), ); default: return ''; } } else { const format = new ol.format.GML3({ srsName: srsName, }); const geometryNode = format.writeGeometryNode(geometry, { dataProjection: srsName, }); if (!geometryNode) { Ext.Logger.warn('Could not serialize geometry'); return null; } const childNodes = geometryNode.children || geometryNode.childNodes; const serializer = new XMLSerializer(); const geomNode = childNodes[0]; const serializedValue = serializer.serializeToString(geomNode); return serializedValue; } }, /** * Reduce an ol.Coordinate array to a string of whitespace * separated coordinate values * @param {Array<ol.Coordinate>} coordArray An array of * coordinates * @return {string} Concatenated array of coordinates */ flattenCoordinates: function (coordArray) { return Ext.Array.map(coordArray, function (cp) { return cp.join(' '); }).join(' '); }, /** * Combines the passed filter bodies with an `<And>` or `<Or>` and * returns them. E.g. created with * GeoExt.util.OGCFilter.getOgcFilterBodyFromExtJsFilterObject * * @param {Array} filterBodies The filter bodies to join. * @param {string} combinator The combinator to use, should be * either `And` (the default) or `Or`. * @param {string} wfsVersion The WFS version to use, either `1.0.0`, * `1.1.0` or `2.0.0` * @return {string} And/Or combined OGC filter bodies. */ combineFilterBodies: function (filterBodies, combinator, wfsVersion) { if ( !Ext.isDefined(filterBodies) || !Ext.isArray(filterBodies) || filterBodies.length === 0 ) { Ext.Logger.error( 'Invalid "filterBodies" argument given to ' + 'GeoExt.util.OGCFilter. You need to pass an array of ' + 'OGC filter bodies as XML string', ); return; } const combineWith = combinator || 'And'; const isWfs20 = !Ext.isEmpty(wfsVersion) && wfsVersion === '2.0.0'; const wfsPrefix = isWfs20 ? 'fes:' : ''; let ogcFilterType = wfsPrefix + combineWith; const openingTag = (ogcFilterType = '<' + ogcFilterType + '>'); const closingTag = Ext.String.insert(openingTag, '/', 1); let combinedFilterBodies = ''; // only use an And/Or filter when there are multiple filter bodies if (filterBodies.length > 1) { Ext.each(filterBodies, function (filterBody) { combinedFilterBodies += filterBody; }); combinedFilterBodies = openingTag + combinedFilterBodies + closingTag; } else { combinedFilterBodies = filterBodies[0]; } return combinedFilterBodies; }, /** * Combines the passed filters with an `<And>` or `<Or>` and * returns them. * * @param {Array} filters The filters to join. * @param {string} combinator The combinator to use, should be * either `And` (the default) or `Or`. * @param {boolean} omitNamespaces Indicates if namespaces * should be omitted in filters, which is useful for WMS * @param {string} wfsVersion The WFS version to use, either `1.0.0`, * `1.1.0` or `2.0.0` * @return {string} An combined OGC filter with the passed filters. */ combineFilters: function (filters, combinator, omitNamespaces, wfsVersion) { const staticMe = GeoExt.util.OGCFilter; const defaultCombineWith = 'And'; const combineWith = combinator || defaultCombineWith; const numFilters = filters.length; const parts = []; const ns = omitNamespaces ? '' : 'ogc'; let omitNamespaceFromWfsVersion = !wfsVersion || wfsVersion === '1.0.0'; if ( !Ext.isEmpty(wfsVersion) && wfsVersion === '2.0.0' && !omitNamespaces ) { parts.push(staticMe.filter20StartElementStr); } else { parts.push( '<Filter' + (omitNamespaces ? '' : ' xmlns="http://www.opengis.net/' + ns + '"' + ' xmlns:gml="http://www.opengis.net/gml"') + '>', ); omitNamespaceFromWfsVersion = true; } parts.push(); if (numFilters > 1) { parts.push( '<' + (omitNamespaces || omitNamespaceFromWfsVersion ? '' : 'fes:') + combineWith + '>', ); } Ext.each(filters, function (filter) { parts.push(filter); }); if (numFilters > 1) { parts.push( '</' + (omitNamespaces || omitNamespaceFromWfsVersion ? '' : 'fes:') + combineWith + '>', ); } parts.push( '</' + (omitNamespaces || omitNamespaceFromWfsVersion ? '' : 'fes:') + 'Filter>', ); return parts.join(''); }, /** * Create an instance of {Ext.util.Filter} that contains the required * information on spatial filter, e.g. operator and geometry * * @param {string} operator The spatial / toplogical operator * @param {string} typeName The name of geometry field * @param {ol.geom.Geometry} value The geometry to use for filtering * @param {string} srsName The EPSG code of the geometry * * @return {Ext.util.Filter} A 'spatial' {Ext.util.Filter} */ createSpatialFilter: function (operator, typeName, value, srsName) { if ( !Ext.Array.contains( GeoExt.util.OGCFilter.topologicalOrSpatialFilterOperators, operator, ) ) { return null; } // construct an instance of Filter return new Ext.util.Filter({ type: 'spatial', srsName: srsName, operator: operator, property: typeName, value: value, }); }, }, });