| 1 | |
| 2 | = !MapGuide RFC 163 - GeoJSON support for WFS/WMS = |
| 3 | |
| 4 | This page contains a change request (RFC) for the !MapGuide Open Source project. |
| 5 | More !MapGuide RFCs can be found on the [wiki:MapGuideRfcs RFCs] page. |
| 6 | |
| 7 | == Status == |
| 8 | |
| 9 | ||RFC Template Version||(1.0)|| |
| 10 | ||Submission Date||14 Nov 2017|| |
| 11 | ||Last Modified||14 Nov 2017|| |
| 12 | ||Author||Jackie Ng|| |
| 13 | ||RFC Status||draft|| |
| 14 | ||Implementation Status||pending|| |
| 15 | ||Proposed Milestone||3.3|| |
| 16 | ||Assigned PSC guide(s)||(when determined)|| |
| 17 | ||'''Voting History'''||(vote date)|| |
| 18 | ||+1|||| |
| 19 | ||+0|||| |
| 20 | ||-0|||| |
| 21 | ||-1|||| |
| 22 | ||no vote|| || |
| 23 | |
| 24 | == Overview == |
| 25 | |
| 26 | This RFC proposes to add support for GeoJSON as an output format for: |
| 27 | |
| 28 | * WFS `GetFeatures` |
| 29 | * WMS `GetFeatureInfo` |
| 30 | |
| 31 | Along with required infrastructure changes needed to support the above 2 cases. |
| 32 | |
| 33 | == Motivation == |
| 34 | |
| 35 | WFS/WMS support in MapGuide is very bare-bones. The usability of WFS/WMS services can be greatly enhanced with support for GeoJSON as an output format, especially for WMS `GetFeatureInfo`, where a GeoJSON response can function as a client-side feature selection overlay with geometry data present. |
| 36 | |
| 37 | == Proposed Solution == |
| 38 | |
| 39 | `application/json` will be advertised as a supported output format for WFS `GetFeatures` and WMS `GetFeatureInfo` |
| 40 | |
| 41 | === GeoJSON Support for WFS GetFeatures === |
| 42 | |
| 43 | The current `GetWfsFeature` method of `MgFeatureService` is the method that WFS `GetFeatures` ultimately calls to return data for feature data in a WFS `GetFeatures` response. |
| 44 | |
| 45 | However this method is not re-usable for implementing alternate output formats for WFS `GetFeatures` because the API itself is GML-centric: |
| 46 | |
| 47 | * Various input parameters have GML-isms |
| 48 | * The return value is an `MgByteReader` with GML content |
| 49 | |
| 50 | To support GeoJSON output for WFS `GetFeatures`, we need a method that gives us the underlying feature reader primitive, which we can then convert to our desired output format of choice in the Web Tier. |
| 51 | |
| 52 | We'll add a new `GetWfsReader` method for this very purpose: |
| 53 | |
| 54 | {{{ |
| 55 | class MG_PLATFORMBASE_API MgFeatureService : public MgService |
| 56 | { |
| 57 | PUBLISHED_API: |
| 58 | //////////////////////////////////////////////////////////////////////////////////////////////////////// |
| 59 | /// \brief |
| 60 | /// Retrieves feature information based on the supplied criteria. |
| 61 | /// |
| 62 | /// \note1 |
| 63 | /// |
| 64 | /// <!-- Syntax in .Net, Java, and PHP --> |
| 65 | /// \htmlinclude DotNetSyntaxTop.html |
| 66 | /// virtual MgFeatureReader GetWfsReader(MgResourceIdentifier featureSourceId, string featureClass, MgStringCollection requiredProperties, string srs, string filter, int maxFeatures, string outputFormat, string sortCriteria); |
| 67 | /// \htmlinclude SyntaxBottom.html |
| 68 | /// \htmlinclude JavaSyntaxTop.html |
| 69 | /// virtual MgFeatureReader GetWfsReader(MgResourceIdentifier featureSourceId, string featureClass, MgStringCollection requiredProperties, string srs, string filter, int maxFeatures, string outputFormat, string sortCriteria); |
| 70 | /// \htmlinclude SyntaxBottom.html |
| 71 | /// \htmlinclude PHPSyntaxTop.html |
| 72 | /// virtual MgFeatureReader GetWfsReader(MgResourceIdentifier featureSourceId, string featureClass, MgStringCollection requiredProperties, string srs, string filter, int maxFeatures, string outputFormat, string sortCriteria); |
| 73 | /// \htmlinclude SyntaxBottom.html |
| 74 | /// |
| 75 | /// \param featureSourceId (MgResourceIdentifier) |
| 76 | /// The resource identifier defining the |
| 77 | /// location of the feature source in |
| 78 | /// the repository. |
| 79 | /// \param featureClass (String/string) |
| 80 | /// The feature class containing the features to retrieve. |
| 81 | /// \param requiredProperties (MgStringCollection) |
| 82 | /// The collection of properties to retrieve for each feature. If the |
| 83 | /// collection is null or empty, all properties will be retrieved. |
| 84 | /// \param srs (String/string) |
| 85 | /// The spatial reference system in which to return feature geometries |
| 86 | /// \param filter (String/string) |
| 87 | /// An XML string containing the definition for an OGC filter |
| 88 | /// \param sortCriteria (String/string) |
| 89 | /// A string identifying the sort criteria |
| 90 | /// |
| 91 | /// \remarks |
| 92 | /// The purpose of this method, as opposed to GetWfsFeature is to return the base reader using the same input parameters |
| 93 | /// allowing for the caller to determine the desired output format and the desired content transformations required from |
| 94 | /// the reader. |
| 95 | /// |
| 96 | /// The main use case for this method is for providing the base feature reader for outputting WFS GetFeature responses in |
| 97 | /// formats other than GML |
| 98 | /// |
| 99 | /// \return |
| 100 | /// Returns an MgByteReader containing the requested feature information. |
| 101 | /// |
| 102 | /// \exception MgInvalidArgumentException |
| 103 | /// |
| 104 | /// \since 3.3 |
| 105 | virtual MgFeatureReader* GetWfsReader(MgResourceIdentifier* featureSourceId, |
| 106 | CREFSTRING featureClass, |
| 107 | MgStringCollection* requiredProperties, |
| 108 | CREFSTRING srs, |
| 109 | CREFSTRING filter, |
| 110 | CREFSTRING sortCriteria) = 0; |
| 111 | }; |
| 112 | }}} |
| 113 | |
| 114 | From the Web Tier, the `MgHttpWfsGetFeature` implementation will call this method instead of `MgFeatureService::GetWfsFeature` if the `INFO_FORMAT` parameter passed in is `application/json`. The returned reader is then housed in our existing GeoJSON output adapter (introduced in MapGuideRfc158) to convert the feature reader data to GeoJSON. |
| 115 | |
| 116 | === GeoJSON Support for WMS GetFeatureInfo === |
| 117 | |
| 118 | For WMS `GetFeatureInfo`, there are cases where being able to return geometry data is useful. However there is only one format where the geometry data would be consumable: GeoJSON. |
| 119 | |
| 120 | To support GeoJSON output, we must first support the ability to output geometry data in a WMS `GetFeatureInfo`. The `QueryFeatureProperties` method of `MgRenderingService` is the method that provides this data. We will add a new internal overload that allows including geometry data as `MgGeometryProperty` instances for each `MgPropertyCollection` of the returned `MgBatchPropertyCollection` |
| 121 | |
| 122 | {{{ |
| 123 | class MG_MAPGUIDE_API MgRenderingService : public MgService |
| 124 | { |
| 125 | INTERNAL_API: |
| 126 | ///////////////////////////////////////////////////////////////// |
| 127 | /// \brief |
| 128 | /// The QueryFeatureProperties operation identifies those features that |
| 129 | /// meet the specified spatial selection criteria. This operation |
| 130 | /// is used to implement WMS feature info and returns property values |
| 131 | /// for all features which match the spatial query |
| 132 | /// |
| 133 | /// \param map |
| 134 | /// Input |
| 135 | /// map object containing current state of map. |
| 136 | /// \param layerNames |
| 137 | /// Input |
| 138 | /// Active layer names for which to query features |
| 139 | /// \param filterGeometry |
| 140 | /// Input |
| 141 | /// geometry object specifying the selection area |
| 142 | /// \param selectionVariant |
| 143 | /// Input |
| 144 | /// selection criterion - integer value corresponding to one of |
| 145 | /// the MgFeatureSpatialOperations values |
| 146 | /// \param featureFilter |
| 147 | /// Input |
| 148 | /// an XML selection string containing the required feature IDs |
| 149 | /// \param maxFeatures |
| 150 | /// Input |
| 151 | /// the maximum number of features to return |
| 152 | /// \param layerAttributeFilter |
| 153 | /// Input |
| 154 | /// bitmask values - 1=Visible, 2=Selectable, 4=HasTooltips |
| 155 | /// \param bIncludeFeatureBBOX |
| 156 | /// Input |
| 157 | /// Indicates whether a bounding box should be included. If true, bounding box is recorded as a special property named _MgFeatureBoundingBox |
| 158 | /// \param bIncludeGeometry |
| 159 | /// Input |
| 160 | /// Indicates whether a bounding box should be included. If true, bounding box is recorded as a special property named _MgFeatureBoundingBox |
| 161 | /// |
| 162 | /// \return |
| 163 | /// An MgSelection instance identifying the features that meet the |
| 164 | /// selection criteria. Returns null if no features are identified. |
| 165 | /// |
| 166 | virtual MgBatchPropertyCollection* QueryFeatureProperties( |
| 167 | MgMap* map, |
| 168 | MgStringCollection* layerNames, |
| 169 | MgGeometry* filterGeometry, |
| 170 | INT32 selectionVariant, |
| 171 | CREFSTRING featureFilter, |
| 172 | INT32 maxFeatures, |
| 173 | INT32 layerAttributeFilter, |
| 174 | bool bIncludeFeatureBBOX, |
| 175 | bool bIncludeGeometry) = 0; |
| 176 | }; |
| 177 | }}} |
| 178 | |
| 179 | The geometry data will be stored as a `MgGeometryProperty` instance within each child `MgPropertyCollection` of the `MgBatchPropertyCollection` as a special property named `_MgGeometry` |
| 180 | |
| 181 | The `MgHttpWmsGetFeatureInfo` class in the `HttpHandler` project will be modified to call this new overload. |
| 182 | |
| 183 | === New OGC XML template engine directives and definitions === |
| 184 | |
| 185 | Now that we have the infrastructure to return geometry data, we'll add new directives and definitions in the OGC XML templating engine that can detect and output this geometry data. |
| 186 | |
| 187 | * `EnumFeatureGeometries` - Enumerates all geometry properties of the current feature. Will only enumerate once at most for each feature if geometry data is present. |
| 188 | * `FeatureInfo.IsLast` - Defines if the current feature is the last one in the iteration. This is needed for GeoJSON so we know when it is needed to insert the delimiting `,` between GeoJSON features. |
| 189 | * `FeatureProperty.IsLast` - Defines if the current property is the last one in the iteration. This is needed for GeoJSON so we know when it is needed to insert the delimiting `,` between GeoJSON properties. |
| 190 | * `FeatureGeometry.Value` - Defines the geometry value. The source of the definition (A new `MgWmsFeatureGeometry` class) will output a GeoJSON geometry fragment if the `INFO_FORMAT` is `application/json`. Otherwise it outputs the geometry as WKT text. |
| 191 | |
| 192 | To illustrate how these directives and definitions are used. These are the additions to the WMS XML template (all versions) to support GeoJSON output for WMS `GetFeatureInfo` |
| 193 | |
| 194 | {{{ |
| 195 | <!-- WMS GetFeatureInfo GeoJSON response body --> |
| 196 | <Response request="GetFeatureInfo" content-type="application/json">{ |
| 197 | "type": "FeatureCollection", |
| 198 | "features": [<?EnumFeatureInfo using="&FeatureInfo.json;" ?>] |
| 199 | }</Response> |
| 200 | |
| 201 | <!-- Definition for a GeoJSON feature --> |
| 202 | <Define item="FeatureInfo.json">{ |
| 203 | "type": "Feature", |
| 204 | "properties": {<?EnumFeatureProperties using="&FeatureProperty.json;" ?>} |
| 205 | <?EnumFeatureGeometries using="&FeatureGeometry.json;" ?> |
| 206 | }<?If l="&FeatureInfo.IsLast" op="eq" r="0"?>,<?Endif?></Define> |
| 207 | |
| 208 | <!-- Definition for a GeoJSON property --> |
| 209 | <Define item="FeatureProperty.json">"&FeatureProperty.Name;": "&FeatureProperty.Value;"<?If l="&FeatureProperty.IsLast" op="eq" r="0"?>,<?Endif?></Define> |
| 210 | |
| 211 | <!-- Definition for a GeoJSON geometry --> |
| 212 | <Define item="FeatureGeometry.json">,"geometry": &FeatureGeometry.Value;</Define> |
| 213 | }}} |
| 214 | |
| 215 | To ensure formats other than GeoJSON can output geometry data, `GetFeatureInfo` response templates for other templates will be modified as such: |
| 216 | |
| 217 | * `text/plain` - Geometry data is output as WKT on another line item |
| 218 | * `text/html` - Geometry data is output as WKT in its own `Geometry` table cell value |
| 219 | * `text/xml` - Geometry data is output as WKT in its own `<Property>` element named `Geometry` |
| 220 | |
| 221 | === Configurable Geometry output === |
| 222 | |
| 223 | Although we now enable geometry output for WMS `GetFeatureInfo` with the RFC, it may not be desirable to have this enabled unconditionally. |
| 224 | |
| 225 | To control whether to emit geometry data in a WMS `GetFeatureInfo` response, we define a new `_EnableGeometry` simple metadata property in the resource header XML of a Layer Definition. If set to `1` in a layer's resource header XML, the WMS `GetFeatureInfo` requests against this layer will include geometry data in its response, otherwise geometry data is omitted. |
| 226 | |
| 227 | The `_EnableGeometry` property has no effect if the `_Queryable` property is not set to `1` (ie. The layer is not published for WMS consumption). |
| 228 | |
| 229 | As this is a new metadata property, this has the effect of geometry output for WMS `GetFeatureInfo` being opt-in. By default, no geometry data is output unless `_EnableGeometry` is defined in the layer resource header with a value of `1` (in addition to `_Queryable` being set to `1`) |
| 230 | |
| 231 | == Implications == |
| 232 | |
| 233 | These are new output formats advertised in WFS and WMS capabilities. Existing output formats are not affected. |
| 234 | |
| 235 | Geometry output is opt-in, so existing WMS `GetFeatureInfo` response will look as they are until the user *chooses* to enable geometry output. |
| 236 | |
| 237 | == Test Plan == |
| 238 | |
| 239 | Verify GeoJSON output is present for `GetFeature` for all supported WFS versions. |
| 240 | |
| 241 | Verify GeoJSON output is present for `GetFeatureInfo` for all supported WMS versions. |
| 242 | |
| 243 | Verify geometry data is output only for WMS published layer where `_EnableGeometry` is set to `1` |
| 244 | |
| 245 | == Funding / Resources == |
| 246 | |
| 247 | Community |