[mapguide-commits] r10021 - in trunk/MgDev: Common/Foundation/Data Common/PlatformBase/Services Server/src/Services/Feature Web/src/HttpHandler Web/src/WebTestRunner

svn_mapguide at osgeo.org svn_mapguide at osgeo.org
Tue Dec 13 01:05:55 PST 2022


Author: jng
Date: 2022-12-13 01:05:54 -0800 (Tue, 13 Dec 2022)
New Revision: 10021

Added:
   trunk/MgDev/Web/src/WebTestRunner/GeoJsonTest.cpp
   trunk/MgDev/Web/src/WebTestRunner/OgcTest.cpp
Removed:
   trunk/MgDev/Web/src/WebTestRunner/OgcWfsTest.cpp
Modified:
   trunk/MgDev/Common/Foundation/Data/ByteSink.cpp
   trunk/MgDev/Common/PlatformBase/Services/GeoJsonWriter.cpp
   trunk/MgDev/Common/PlatformBase/Services/GeoJsonWriter.h
   trunk/MgDev/Server/src/Services/Feature/WfsQueryAdapter.cpp
   trunk/MgDev/Web/src/HttpHandler/HttpSelectFeatures.cpp
   trunk/MgDev/Web/src/HttpHandler/HttpSelectFeaturesSpatially.cpp
   trunk/MgDev/Web/src/HttpHandler/HttpUtil.cpp
   trunk/MgDev/Web/src/HttpHandler/HttpUtil.h
   trunk/MgDev/Web/src/HttpHandler/HttpWfsGetFeature.cpp
   trunk/MgDev/Web/src/HttpHandler/ReaderByteSourceImpl.cpp
   trunk/MgDev/Web/src/HttpHandler/ReaderByteSourceImpl.h
   trunk/MgDev/Web/src/WebTestRunner/WebTestRunner.vcxproj
   trunk/MgDev/Web/src/WebTestRunner/WebTestRunner.vcxproj.filters
Log:
When outputting GeoJSON from our mapagent, always try to write the "crs" property if the SRS of the feature source we're writing the GeoJSON from has an EPSG code representation

Also fix application/json mime type not being set for GeoJSON representations of WFS GetFeatures

Also make sure that the server implementation of MgFeatureService::GetWfsReader() applies coordsys transforms if one is constructed in the MgWfsQueryAdapter. This means that GeoJSON representations of WFS GetFeatures are properly transformed like the other supported formats.

Fixes #2809
Fixes #2857
Fixes #2856

Modified: trunk/MgDev/Common/Foundation/Data/ByteSink.cpp
===================================================================
--- trunk/MgDev/Common/Foundation/Data/ByteSink.cpp	2022-11-25 14:02:59 UTC (rev 10020)
+++ trunk/MgDev/Common/Foundation/Data/ByteSink.cpp	2022-12-13 09:05:54 UTC (rev 10021)
@@ -80,6 +80,7 @@
 
     if (!mimeType.empty()
         && mimeType.find(L"text") == STRING::npos
+        && mimeType.find(L"json") == STRING::npos
         && MgMimeType::Mvt != mimeType
         && MgMimeType::Binary != mimeType)
     {

Modified: trunk/MgDev/Common/PlatformBase/Services/GeoJsonWriter.cpp
===================================================================
--- trunk/MgDev/Common/PlatformBase/Services/GeoJsonWriter.cpp	2022-11-25 14:02:59 UTC (rev 10020)
+++ trunk/MgDev/Common/PlatformBase/Services/GeoJsonWriter.cpp	2022-12-13 09:05:54 UTC (rev 10021)
@@ -562,4 +562,60 @@
 bool MgGeoJsonWriter::IsPrecisionEnabled()
 {
     return m_precisionEnabled;
+}
+
+STRING MgGeoJsonWriter::GetCRS(INT32 epsgCode)
+{
+    STRING crs;
+    if (epsgCode != 0)
+    {
+        if (epsgCode == 4326)
+        {
+            crs = L"urn:ogc:def:crs:OGC:1.3:CRS84";
+        }
+        else
+        {
+            STRING sEpsg;
+            MgUtil::Int32ToString(epsgCode, sEpsg);
+            crs = L"urn:ogc:def:crs:EPSG::" + sEpsg;
+        }
+    }
+    return crs;
+}
+
+STRING MgGeoJsonWriter::GetCRS(MgResourceIdentifier* fsId, MgClassDefinition* clsDef, MgFeatureService* featSvc)
+{
+    STRING crs;
+    STRING geomName = clsDef->GetDefaultGeometryPropertyName();
+    if (!geomName.empty())
+    {
+        Ptr<MgPropertyDefinitionCollection> clsProps = clsDef->GetProperties();
+        auto pidx = clsProps->IndexOf(geomName);
+        if (pidx >= 0)
+        {
+            Ptr<MgPropertyDefinition> pd = clsProps->GetItem(pidx);
+            if (pd->GetPropertyType() == MgFeaturePropertyType::GeometricProperty)
+            {
+                auto gd = static_cast<MgGeometricPropertyDefinition*>(pd.p);
+                auto srsName = gd->GetSpatialContextAssociation();
+
+                Ptr<MgSpatialContextReader> scReader = featSvc->GetSpatialContexts(fsId, false);
+                while (scReader->ReadNext())
+                {
+                    if (scReader->GetName() == srsName)
+                    {
+                        auto wkt = scReader->GetCoordinateSystemWkt();
+                        if (!wkt.empty())
+                        {
+                            Ptr<MgCoordinateSystemFactory> csFactory = new MgCoordinateSystemFactory();
+                            auto epsg = csFactory->ConvertWktToEpsgCode(wkt);
+                            crs = GetCRS(epsg);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return crs;
 }
\ No newline at end of file

Modified: trunk/MgDev/Common/PlatformBase/Services/GeoJsonWriter.h
===================================================================
--- trunk/MgDev/Common/PlatformBase/Services/GeoJsonWriter.h	2022-11-25 14:02:59 UTC (rev 10020)
+++ trunk/MgDev/Common/PlatformBase/Services/GeoJsonWriter.h	2022-12-13 09:05:54 UTC (rev 10021)
@@ -194,6 +194,8 @@
     bool IsPrecisionEnabled();
 
 INTERNAL_API:
+    static STRING GetCRS(INT32 epsgCode);
+    static STRING GetCRS(MgResourceIdentifier* fsId, MgClassDefinition* clsDef, MgFeatureService* featSvc);
     STRING GeometryToGeoJson(MgGeometry* geom);
     void ToGeoJson(MgGeometry* geom, REFSTRING str, bool bIncludePrefix = true);
 

Modified: trunk/MgDev/Server/src/Services/Feature/WfsQueryAdapter.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Feature/WfsQueryAdapter.cpp	2022-11-25 14:02:59 UTC (rev 10020)
+++ trunk/MgDev/Server/src/Services/Feature/WfsQueryAdapter.cpp	2022-12-13 09:05:54 UTC (rev 10021)
@@ -213,7 +213,15 @@
     Ptr<MgFeatureReader> fr;
     MG_FEATURE_SERVICE_TRY()
     // TODO: can FeatureName be an extension name rather than a FeatureClass?
-    fr = m_featSvc->SelectFeatures(m_fs, m_className, m_options);
+    if (nullptr != m_xform.p)
+    {
+        Ptr<MgFeatureReader> inner = m_featSvc->SelectFeatures(m_fs, m_className, m_options);
+        fr = new MgTransformedGeometryFeatureReader(inner, m_xform);
+    }
+    else
+    {
+        fr = m_featSvc->SelectFeatures(m_fs, m_className, m_options);
+    }
     MG_FEATURE_SERVICE_CHECK_CONNECTION_CATCH_AND_THROW(m_fs, L"MgWfsQueryAdapter.GetWfsReader")
     return fr.Detach();
 }

Modified: trunk/MgDev/Web/src/HttpHandler/HttpSelectFeatures.cpp
===================================================================
--- trunk/MgDev/Web/src/HttpHandler/HttpSelectFeatures.cpp	2022-11-25 14:02:59 UTC (rev 10020)
+++ trunk/MgDev/Web/src/HttpHandler/HttpSelectFeatures.cpp	2022-12-13 09:05:54 UTC (rev 10021)
@@ -132,7 +132,7 @@
     cfg->GetIntValue(MgConfigProperties::AgentPropertiesSection, MgConfigProperties::AgentGlobalMaxFeatureQueryLimit, limit, MgConfigProperties::DefaultAgentGlobalMaxFeatureQueryLimit);
 
     //MgByteSource owns this and will clean it up when done
-    Ptr<MgTransform> xform;
+    Ptr<MgCoordinateSystemTransform> xform;
     if (!m_transformTo.empty())
     {
         STRING schemaName;
@@ -141,12 +141,34 @@
         xform = MgHttpUtil::GetTransform(service, &resId, schemaName, className, m_transformTo);
     }
 
-    ByteSourceImpl* bsImpl = new MgReaderByteSourceImpl(featureReader, m_responseFormat, m_bCleanJson, m_bEnablePrecision, m_precision, xform);
+    auto bsImpl = new MgReaderByteSourceImpl(featureReader, m_responseFormat, m_bCleanJson, m_bEnablePrecision, m_precision, xform);
     if (limit > 0)
     {
-        static_cast<MgReaderByteSourceImpl*>(bsImpl)->SetMaxFeatures(limit);
+        bsImpl->SetMaxFeatures(limit);
     }
+    // If requesting for clean JSON, we're basically asking for GeoJSON, so try to set
+    // the CRS
+    if (m_bCleanJson)
+    {
+        STRING crs;
+        // We have an xform, output the target CRS
+        if (nullptr != xform.p)
+        {
+            Ptr<MgCoordinateSystem> targetCs = xform->GetTarget();
+            crs = MgGeoJsonWriter::GetCRS(targetCs->GetEpsgCode());
+        }
+        else // No transformation requested, try to output original CRS
+        {
+            Ptr<MgClassDefinition> clsDef = featureReader->GetClassDefinition();
+            crs = MgGeoJsonWriter::GetCRS(&resId, clsDef, service);
+        }
 
+        if (!crs.empty())
+        {
+            bsImpl->SetGeoJsonCRS(MgUtil::WideCharToMultiByte(crs));
+        }
+    }
+
     Ptr<MgByteSource> byteSource = new MgByteSource(bsImpl);
     byteSource->SetMimeType(m_responseFormat);
     Ptr<MgByteReader> byteReader = byteSource->GetReader();

Modified: trunk/MgDev/Web/src/HttpHandler/HttpSelectFeaturesSpatially.cpp
===================================================================
--- trunk/MgDev/Web/src/HttpHandler/HttpSelectFeaturesSpatially.cpp	2022-11-25 14:02:59 UTC (rev 10020)
+++ trunk/MgDev/Web/src/HttpHandler/HttpSelectFeaturesSpatially.cpp	2022-12-13 09:05:54 UTC (rev 10021)
@@ -110,6 +110,8 @@
     Ptr<MgFeatureAggregateOptions> qryOptions = new MgFeatureAggregateOptions();
     qryOptions->SetFilter(filterText);
 
+    bool bRequestedSpatialExtents = false;
+
     if (featureProps != NULL)
     {
         INT32 cnt = featureProps->GetCount();
@@ -128,7 +130,7 @@
         {
             for (INT32 j=0; j < aliasCnt; j++)
             {
-                qryOptions->AddComputedProperty(aliases->GetItem(j), computedProps->GetItem(j));
+                qryOptions->AddComputedProperty(aliases->GetItem(j), computedProps->GetItem(j));                
             }
         }
         else
@@ -137,19 +139,55 @@
         }
     }
 
+    // Test if this is a single SpatialExtents() aggregate query
+    Ptr<MgStringPropertyCollection> computed = qryOptions->GetComputedProperties();
+    if (computed->GetCount() == 1)
+    {
+        Ptr<MgStringProperty> comp = computed->GetItem(0);
+        auto expr = comp->GetValue();
+        // HACK: We don't have access to FDO in this library to parse this expression
+        // here, so we're doing the crude version of seeing if this is a SpatialExtents()
+        // expr by just seeing if it contains that function name
+        if (expr.find(L"SpatialExtents") != STRING::npos)
+        {
+            bRequestedSpatialExtents = true;
+        }
+    }
+
     Ptr<MgDataReader> dataReader = service->SelectAggregate(&resId, m_className, qryOptions);
     //MgByteSource owns this and will clean it up when done
-    Ptr<MgTransform> xform;
+    Ptr<MgCoordinateSystemTransform> xform;
+    STRING schemaName;
+    STRING className;
+    MgUtil::ParseQualifiedClassName(m_className, schemaName, className);
     if (!m_transformTo.empty())
     {
-        STRING schemaName;
-        STRING className;
-        MgUtil::ParseQualifiedClassName(m_className, schemaName, className);
-
         xform = MgHttpUtil::GetTransform(service, &resId, schemaName, className, m_transformTo);
     }
-    ByteSourceImpl* bsImpl = new MgReaderByteSourceImpl(dataReader, m_responseFormat, m_bCleanJson, m_bEnablePrecision, m_precision, xform);
+    auto bsImpl = new MgReaderByteSourceImpl(dataReader, m_responseFormat, m_bCleanJson, m_bEnablePrecision, m_precision, xform);
+    // If only asking for SpatialExtents() and requesting for clean JSON, we're 
+    // basically asking for GeoJSON, so try to set the CRS
+    if (bRequestedSpatialExtents && m_bCleanJson)
+    {
+        STRING crs;
+        // We have an xform, output the target CRS
+        if (nullptr != xform.p)
+        {
+            Ptr<MgCoordinateSystem> targetCs = xform->GetTarget();
+            crs = MgGeoJsonWriter::GetCRS(targetCs->GetEpsgCode());
+        }
+        else // No transformation requested, try to output original CRS
+        {
+            Ptr<MgClassDefinition> clsDef = service->GetClassDefinition(&resId, schemaName, className);
+            crs = MgGeoJsonWriter::GetCRS(&resId, clsDef, service);
+        }
 
+        if (!crs.empty())
+        {
+            bsImpl->SetGeoJsonCRS(MgUtil::WideCharToMultiByte(crs));
+        }
+    }
+
     Ptr<MgByteSource> byteSource = new MgByteSource(bsImpl);
     byteSource->SetMimeType(m_responseFormat);
     Ptr<MgByteReader> byteReader = byteSource->GetReader();

Modified: trunk/MgDev/Web/src/HttpHandler/HttpUtil.cpp
===================================================================
--- trunk/MgDev/Web/src/HttpHandler/HttpUtil.cpp	2022-11-25 14:02:59 UTC (rev 10020)
+++ trunk/MgDev/Web/src/HttpHandler/HttpUtil.cpp	2022-12-13 09:05:54 UTC (rev 10021)
@@ -87,9 +87,9 @@
     }
 }
 
-MgTransform* MgHttpUtil::GetTransform(MgFeatureService* featSvc, MgResourceIdentifier* resId, CREFSTRING schemaName, CREFSTRING className, CREFSTRING transformTo)
+MgCoordinateSystemTransform* MgHttpUtil::GetTransform(MgFeatureService* featSvc, MgResourceIdentifier* resId, CREFSTRING schemaName, CREFSTRING className, CREFSTRING transformTo)
 {
-    Ptr<MgTransform> transform;
+    Ptr<MgCoordinateSystemTransform> transform;
 
     MG_HTTP_HANDLER_TRY()
 

Modified: trunk/MgDev/Web/src/HttpHandler/HttpUtil.h
===================================================================
--- trunk/MgDev/Web/src/HttpHandler/HttpUtil.h	2022-11-25 14:02:59 UTC (rev 10020)
+++ trunk/MgDev/Web/src/HttpHandler/HttpUtil.h	2022-12-13 09:05:54 UTC (rev 10021)
@@ -74,7 +74,7 @@
 
     static void LogException(MgException* exception);
 
-    static MgTransform* GetTransform(MgFeatureService* featSvc, MgResourceIdentifier* resId, CREFSTRING schemaName, CREFSTRING className, CREFSTRING transformTo);
+    static MgCoordinateSystemTransform* GetTransform(MgFeatureService* featSvc, MgResourceIdentifier* resId, CREFSTRING schemaName, CREFSTRING className, CREFSTRING transformTo);
 };
 
 #endif

Modified: trunk/MgDev/Web/src/HttpHandler/HttpWfsGetFeature.cpp
===================================================================
--- trunk/MgDev/Web/src/HttpHandler/HttpWfsGetFeature.cpp	2022-11-25 14:02:59 UTC (rev 10020)
+++ trunk/MgDev/Web/src/HttpHandler/HttpWfsGetFeature.cpp	2022-12-13 09:05:54 UTC (rev 10021)
@@ -351,15 +351,31 @@
                                 //For GeoJSON, use the new GetWfsReader API
                                 if (sOutputFormat == MgMimeType::Json)
                                 {
+                                    auto srs = m_getFeatureParams->GetSrs();
+
                                     // NOTE: This API doesn't accept WFS version, format and XML namepaces because these are GML-isms baked into the GetWfsFeature API itself, making
                                     // it unsuitable for non-GML output, hence the need for this new GetWfsReader API
                                     Ptr<MgFeatureReader> fr = featureService->GetWfsReader(featureSourceId, ((sSchemaHash.size() == 0) ? sClass : sSchemaHash + _(":") + sClass),
-                                        requiredProperties, m_getFeatureParams->GetSrs(), filter, sSortCriteria);
+                                        requiredProperties, srs, filter, sSortCriteria);
 
                                     //MgByteSource owns this and will clean it up when done
                                     MgReaderByteSourceImpl* bsImpl = new MgReaderByteSourceImpl(fr, MgMimeType::Json, true, false, -1, NULL);
                                     bsImpl->SetMaxFeatures(numFeaturesToRetrieve - 1);
+                                    
+                                    if (!srs.empty())
+                                    {
+                                        Ptr<MgCoordinateSystemFactory> csFactory = new MgCoordinateSystemFactory();
+                                        Ptr<MgCoordinateSystem> cs = csFactory->Create(srs);
+
+                                        auto crs = MgGeoJsonWriter::GetCRS(cs->GetEpsgCode());
+                                        if (!crs.empty())
+                                        {
+                                            bsImpl->SetGeoJsonCRS(MgUtil::WideCharToMultiByte(crs));
+                                        }
+                                    }
+
                                     Ptr<MgByteSource> bs = new MgByteSource(bsImpl);
+                                    bs->SetMimeType(MgMimeType::Json);
                                     resultReader = bs->GetReader();
                                 }
                                 else

Modified: trunk/MgDev/Web/src/HttpHandler/ReaderByteSourceImpl.cpp
===================================================================
--- trunk/MgDev/Web/src/HttpHandler/ReaderByteSourceImpl.cpp	2022-11-25 14:02:59 UTC (rev 10020)
+++ trunk/MgDev/Web/src/HttpHandler/ReaderByteSourceImpl.cpp	2022-12-13 09:05:54 UTC (rev 10021)
@@ -96,6 +96,12 @@
                 //   "features": [
                 jsonbuf = "{";
                 jsonbuf += " \"type\": \"FeatureCollection\",";
+
+                if (!m_crs.empty())
+                {
+                    jsonbuf += " \"crs\": { \"type\": \"name\", \"properties\": { \"name\" : \"" + m_crs + "\" } },";
+                }
+
                 jsonbuf += " \"features\": [";
 
                 m_buf += jsonbuf;
@@ -367,5 +373,10 @@
 ///
 void MgReaderByteSourceImpl::Rewind()
 {
-    new MgException(MgExceptionCodes::MgInvalidOperationException, L"MgReaderByteSourceImpl.Rewind", __LINE__, __WFILE__, NULL, L"", NULL);
+    throw new MgException(MgExceptionCodes::MgInvalidOperationException, L"MgReaderByteSourceImpl.Rewind", __LINE__, __WFILE__, NULL, L"", NULL);
+}
+
+void MgReaderByteSourceImpl::SetGeoJsonCRS(std::string crs)
+{
+    m_crs = crs;
 }
\ No newline at end of file

Modified: trunk/MgDev/Web/src/HttpHandler/ReaderByteSourceImpl.h
===================================================================
--- trunk/MgDev/Web/src/HttpHandler/ReaderByteSourceImpl.h	2022-11-25 14:02:59 UTC (rev 10020)
+++ trunk/MgDev/Web/src/HttpHandler/ReaderByteSourceImpl.h	2022-12-13 09:05:54 UTC (rev 10021)
@@ -82,6 +82,8 @@
 
     void SetMaxFeatures(INT32 maxFeatures);
 
+    void SetGeoJsonCRS(std::string crs);
+
 private:
     bool ReadNextInternal();
     INT32 ReadInternalBuffer(BYTE_ARRAY_OUT buffer, INT32 fromIndex, INT32 length);
@@ -100,6 +102,9 @@
     std::string m_buf;
     INT32 m_bufOffset;
     INT32 m_precision;
+
+    std::string m_crs;
+
 };
 
 #endif
\ No newline at end of file

Added: trunk/MgDev/Web/src/WebTestRunner/GeoJsonTest.cpp
===================================================================
--- trunk/MgDev/Web/src/WebTestRunner/GeoJsonTest.cpp	                        (rev 0)
+++ trunk/MgDev/Web/src/WebTestRunner/GeoJsonTest.cpp	2022-12-13 09:05:54 UTC (rev 10021)
@@ -0,0 +1,298 @@
+//
+//  Copyright (C) 2004-2022 by Autodesk, Inc.
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of version 2.1 of the GNU Lesser
+//  General Public License as published by the Free Software Foundation.
+//
+//  This library 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
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+//
+
+#include "catch.hpp"
+#include "MapGuideCommon.h"
+#include "HttpHandler.h"
+
+TEST_CASE("SelectFeatures_GeoJson_CRS_WGS84", "[FeatureService]")
+{
+    
+    try
+    {
+        // Load the test data
+        Ptr<MgSiteConnection> conn = new MgSiteConnection();
+        Ptr<MgUserInformation> userInfo = new MgUserInformation(L"Anonymous", L"");
+        conn->Open(userInfo);
+
+        Ptr<MgSite> site = conn->GetSite();
+        auto sessionId = site->CreateSession();
+
+        userInfo->SetMgSessionId(sessionId);
+
+        Ptr<MgResourceService> resSvc = static_cast<MgResourceService*>(conn->CreateService(MgServiceType::ResourceService));
+        Ptr<MgResourceIdentifier> fsId = new MgResourceIdentifier(L"Session:" + sessionId + L"//Districts.FeatureSource");
+
+        Ptr<MgByteSource> testSource = new MgByteSource(L"TestData/Sheboygan_VotingDistricts.FeatureSource");
+        Ptr<MgByteReader> testRdr = testSource->GetReader();
+
+        resSvc->SetResource(fsId, testRdr, nullptr);
+
+        Ptr<MgByteSource> testDataSource = new MgByteSource(L"TestData/Sheboygan_VotingDistricts.sdf");
+        Ptr<MgByteReader> testDataRdr = testDataSource->GetReader();
+
+        resSvc->SetResourceData(fsId, L"Sheboygan_VotingDistricts.sdf", MgResourceDataType::File, testDataRdr);
+
+        // Now do the actual test
+        STRING uri = L"";
+        Ptr<MgHttpRequest> req = new MgHttpRequest(uri);
+        Ptr<MgHttpRequestParam> param = req->GetRequestParam();
+
+        param->AddParameter(L"SESSION", sessionId);
+
+        param->AddParameter(L"OPERATION", L"SELECTFEATURES");
+        param->AddParameter(L"CLEAN", L"1");
+        param->AddParameter(L"VERSION", L"4.0.0");
+        param->AddParameter(L"LOCALE", L"en");
+        param->AddParameter(L"FORMAT", L"application/json");
+        param->AddParameter(L"RESOURCEID", fsId->ToString());
+        param->AddParameter(L"CLASSNAME", L"Default:VotingDistricts");
+        param->AddParameter(L"TRANSFORMTO", L"LL84");
+
+        Ptr<MgHttpResponse> resp = req->Execute();
+        Ptr<MgHttpResult> result = resp->GetResult();
+
+        STRING err = result->GetDetailedErrorMessage();
+        REQUIRE(err.empty());
+
+        Ptr<MgByteReader> reader = dynamic_cast<MgByteReader*>(result->GetResultObject());
+        REQUIRE(reader.p != nullptr);
+        REQUIRE((L"application/json" == result->GetResultContentType()));
+
+        STRING respContent = reader->ToString();
+        // This CRS string should be in the GeoJSON
+        REQUIRE(respContent.find(L"urn:ogc:def:crs:OGC:1.3:CRS84") != STRING::npos);
+
+        // Cleanup
+        resSvc->DeleteResource(fsId);
+    }
+    catch (MgException* ex)
+    {
+        auto st = ex->GetStackTrace();
+        auto msg = ex->GetExceptionMessage();
+        SAFE_RELEASE(ex);
+    }
+}
+
+TEST_CASE("SelectFeatures_GeoJson_CRS_WebMercator", "[FeatureService]")
+{
+    try
+    {
+        // Load the test data
+        Ptr<MgSiteConnection> conn = new MgSiteConnection();
+        Ptr<MgUserInformation> userInfo = new MgUserInformation(L"Anonymous", L"");
+        conn->Open(userInfo);
+
+        Ptr<MgSite> site = conn->GetSite();
+        auto sessionId = site->CreateSession();
+
+        userInfo->SetMgSessionId(sessionId);
+
+        Ptr<MgResourceService> resSvc = static_cast<MgResourceService*>(conn->CreateService(MgServiceType::ResourceService));
+        Ptr<MgResourceIdentifier> fsId = new MgResourceIdentifier(L"Session:" + sessionId + L"//Districts.FeatureSource");
+
+        Ptr<MgByteSource> testSource = new MgByteSource(L"TestData/Sheboygan_VotingDistricts.FeatureSource");
+        Ptr<MgByteReader> testRdr = testSource->GetReader();
+
+        resSvc->SetResource(fsId, testRdr, nullptr);
+
+        Ptr<MgByteSource> testDataSource = new MgByteSource(L"TestData/Sheboygan_VotingDistricts.sdf");
+        Ptr<MgByteReader> testDataRdr = testDataSource->GetReader();
+
+        resSvc->SetResourceData(fsId, L"Sheboygan_VotingDistricts.sdf", MgResourceDataType::File, testDataRdr);
+
+        // Now do the actual test
+        STRING uri = L"";
+        Ptr<MgHttpRequest> req = new MgHttpRequest(uri);
+        Ptr<MgHttpRequestParam> param = req->GetRequestParam();
+
+        param->AddParameter(L"SESSION", sessionId);
+
+        param->AddParameter(L"OPERATION", L"SELECTFEATURES");
+        param->AddParameter(L"CLEAN", L"1");
+        param->AddParameter(L"VERSION", L"4.0.0");
+        param->AddParameter(L"LOCALE", L"en");
+        param->AddParameter(L"FORMAT", L"application/json");
+        param->AddParameter(L"RESOURCEID", fsId->ToString());
+        param->AddParameter(L"CLASSNAME", L"Default:VotingDistricts");
+        param->AddParameter(L"TRANSFORMTO", L"WGS84.PseudoMercator");
+
+        Ptr<MgHttpResponse> resp = req->Execute();
+        Ptr<MgHttpResult> result = resp->GetResult();
+
+        STRING err = result->GetDetailedErrorMessage();
+        REQUIRE(err.empty());
+
+        Ptr<MgByteReader> reader = dynamic_cast<MgByteReader*>(result->GetResultObject());
+        REQUIRE(reader.p != nullptr);
+        REQUIRE((L"application/json" == result->GetResultContentType()));
+
+        STRING respContent = reader->ToString();
+        // This CRS string should be in the GeoJSON
+        REQUIRE(respContent.find(L"urn:ogc:def:crs:EPSG::3857") != STRING::npos);
+
+        // Cleanup
+        resSvc->DeleteResource(fsId);
+    }
+    catch (MgException* ex)
+    {
+        auto msg = ex->GetExceptionMessage();
+        SAFE_RELEASE(ex);
+    }
+}
+
+TEST_CASE("SpatialExtents_GeoJson_CRS_WGS84", "[FeatureService]")
+{
+
+    try
+    {
+        // Load the test data
+        Ptr<MgSiteConnection> conn = new MgSiteConnection();
+        Ptr<MgUserInformation> userInfo = new MgUserInformation(L"Anonymous", L"");
+        conn->Open(userInfo);
+
+        Ptr<MgSite> site = conn->GetSite();
+        auto sessionId = site->CreateSession();
+
+        userInfo->SetMgSessionId(sessionId);
+
+        Ptr<MgResourceService> resSvc = static_cast<MgResourceService*>(conn->CreateService(MgServiceType::ResourceService));
+        Ptr<MgResourceIdentifier> fsId = new MgResourceIdentifier(L"Session:" + sessionId + L"//Districts.FeatureSource");
+
+        Ptr<MgByteSource> testSource = new MgByteSource(L"TestData/Sheboygan_VotingDistricts.FeatureSource");
+        Ptr<MgByteReader> testRdr = testSource->GetReader();
+
+        resSvc->SetResource(fsId, testRdr, nullptr);
+
+        Ptr<MgByteSource> testDataSource = new MgByteSource(L"TestData/Sheboygan_VotingDistricts.sdf");
+        Ptr<MgByteReader> testDataRdr = testDataSource->GetReader();
+
+        resSvc->SetResourceData(fsId, L"Sheboygan_VotingDistricts.sdf", MgResourceDataType::File, testDataRdr);
+
+        // Now do the actual test
+        STRING uri = L"";
+        Ptr<MgHttpRequest> req = new MgHttpRequest(uri);
+        Ptr<MgHttpRequestParam> param = req->GetRequestParam();
+
+        param->AddParameter(L"SESSION", sessionId);
+
+        param->AddParameter(L"OPERATION", L"SELECTAGGREGATES");
+        param->AddParameter(L"CLEAN", L"1");
+        param->AddParameter(L"VERSION", L"4.0.0");
+        param->AddParameter(L"LOCALE", L"en");
+        param->AddParameter(L"FORMAT", L"application/json");
+        param->AddParameter(L"RESOURCEID", fsId->ToString());
+        param->AddParameter(L"CLASSNAME", L"Default:VotingDistricts");
+        param->AddParameter(L"TRANSFORMTO", L"LL84");
+
+        param->AddParameter(L"COMPUTED_ALIASES", L"EXTENT");
+        param->AddParameter(L"COMPUTED_PROPERTIES", L"SpatialExtents(Geometry)");
+
+        Ptr<MgHttpResponse> resp = req->Execute();
+        Ptr<MgHttpResult> result = resp->GetResult();
+
+        STRING err = result->GetDetailedErrorMessage();
+        REQUIRE(err.empty());
+
+        Ptr<MgByteReader> reader = dynamic_cast<MgByteReader*>(result->GetResultObject());
+        REQUIRE(reader.p != nullptr);
+        REQUIRE((L"application/json" == result->GetResultContentType()));
+
+        STRING respContent = reader->ToString();
+        // This CRS string should be in the GeoJSON
+        REQUIRE(respContent.find(L"urn:ogc:def:crs:OGC:1.3:CRS84") != STRING::npos);
+
+        // Cleanup
+        resSvc->DeleteResource(fsId);
+    }
+    catch (MgException* ex)
+    {
+        auto st = ex->GetStackTrace();
+        auto msg = ex->GetExceptionMessage();
+        SAFE_RELEASE(ex);
+    }
+}
+
+TEST_CASE("SpatialExtents_GeoJson_CRS_WebMercator", "[FeatureService]")
+{
+    try
+    {
+        // Load the test data
+        Ptr<MgSiteConnection> conn = new MgSiteConnection();
+        Ptr<MgUserInformation> userInfo = new MgUserInformation(L"Anonymous", L"");
+        conn->Open(userInfo);
+
+        Ptr<MgSite> site = conn->GetSite();
+        auto sessionId = site->CreateSession();
+
+        userInfo->SetMgSessionId(sessionId);
+
+        Ptr<MgResourceService> resSvc = static_cast<MgResourceService*>(conn->CreateService(MgServiceType::ResourceService));
+        Ptr<MgResourceIdentifier> fsId = new MgResourceIdentifier(L"Session:" + sessionId + L"//Districts.FeatureSource");
+
+        Ptr<MgByteSource> testSource = new MgByteSource(L"TestData/Sheboygan_VotingDistricts.FeatureSource");
+        Ptr<MgByteReader> testRdr = testSource->GetReader();
+
+        resSvc->SetResource(fsId, testRdr, nullptr);
+
+        Ptr<MgByteSource> testDataSource = new MgByteSource(L"TestData/Sheboygan_VotingDistricts.sdf");
+        Ptr<MgByteReader> testDataRdr = testDataSource->GetReader();
+
+        resSvc->SetResourceData(fsId, L"Sheboygan_VotingDistricts.sdf", MgResourceDataType::File, testDataRdr);
+
+        // Now do the actual test
+        STRING uri = L"";
+        Ptr<MgHttpRequest> req = new MgHttpRequest(uri);
+        Ptr<MgHttpRequestParam> param = req->GetRequestParam();
+
+        param->AddParameter(L"SESSION", sessionId);
+
+        param->AddParameter(L"OPERATION", L"SELECTAGGREGATES");
+        param->AddParameter(L"CLEAN", L"1");
+        param->AddParameter(L"VERSION", L"4.0.0");
+        param->AddParameter(L"LOCALE", L"en");
+        param->AddParameter(L"FORMAT", L"application/json");
+        param->AddParameter(L"RESOURCEID", fsId->ToString());
+        param->AddParameter(L"CLASSNAME", L"Default:VotingDistricts");
+        param->AddParameter(L"TRANSFORMTO", L"WGS84.PseudoMercator");
+        
+        param->AddParameter(L"COMPUTED_ALIASES", L"EXTENT");
+        param->AddParameter(L"COMPUTED_PROPERTIES", L"SpatialExtents(Geometry)");
+
+        Ptr<MgHttpResponse> resp = req->Execute();
+        Ptr<MgHttpResult> result = resp->GetResult();
+
+        STRING err = result->GetDetailedErrorMessage();
+        REQUIRE(err.empty());
+
+        Ptr<MgByteReader> reader = dynamic_cast<MgByteReader*>(result->GetResultObject());
+        REQUIRE(reader.p != nullptr);
+        REQUIRE((L"application/json" == result->GetResultContentType()));
+
+        STRING respContent = reader->ToString();
+        // This CRS string should be in the GeoJSON
+        REQUIRE(respContent.find(L"urn:ogc:def:crs:EPSG::3857") != STRING::npos);
+
+        // Cleanup
+        resSvc->DeleteResource(fsId);
+    }
+    catch (MgException* ex)
+    {
+        auto msg = ex->GetExceptionMessage();
+        SAFE_RELEASE(ex);
+    }
+}
\ No newline at end of file

Copied: trunk/MgDev/Web/src/WebTestRunner/OgcTest.cpp (from rev 10020, trunk/MgDev/Web/src/WebTestRunner/OgcWfsTest.cpp)
===================================================================
--- trunk/MgDev/Web/src/WebTestRunner/OgcTest.cpp	                        (rev 0)
+++ trunk/MgDev/Web/src/WebTestRunner/OgcTest.cpp	2022-12-13 09:05:54 UTC (rev 10021)
@@ -0,0 +1,128 @@
+//
+//  Copyright (C) 2004-2020 by Autodesk, Inc.
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of version 2.1 of the GNU Lesser
+//  General Public License as published by the Free Software Foundation.
+//
+//  This library 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
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+//
+
+#include "catch.hpp"
+#include "MapGuideCommon.h"
+#include "HttpHandler.h"
+
+TEST_CASE("Wfs_GetFeature_GeoJSON_CRS84", "[Wfs]")
+{
+    STRING uri = L"";
+    Ptr<MgHttpRequest> req = new MgHttpRequest(uri);
+    Ptr<MgHttpRequestParam> param = req->GetRequestParam();
+
+    MgConfiguration* cfg = MgConfiguration::GetInstance();
+    STRING pwd;
+    cfg->GetStringValue(MgConfigProperties::OgcPropertiesSection, MgConfigProperties::WfsPassword, pwd, L"");
+
+    param->AddParameter(L"USERNAME", MgUser::WfsUser);
+    param->AddParameter(L"PASSWORD", pwd);
+
+    param->AddParameter(L"SERVICE", L"WFS");
+    param->AddParameter(L"REQUEST", L"GETFEATURE");
+    param->AddParameter(L"VERSION", L"1.1.0");
+    param->AddParameter(L"TYPENAME", L"ns55197509:VotingDistricts");
+    param->AddParameter(L"MAXFEATURES", L"10");
+    param->AddParameter(L"SRSNAME", L"EPSG:4326");
+    param->AddParameter(L"OUTPUTFORMAT", L"application/json");
+
+    Ptr<MgHttpResponse> resp = req->Execute();
+    Ptr<MgHttpResult> result = resp->GetResult();
+
+    STRING err = result->GetDetailedErrorMessage();
+    REQUIRE(err.empty());
+
+    Ptr<MgByteReader> reader = dynamic_cast<MgByteReader*>(result->GetResultObject());
+    REQUIRE(reader.p != nullptr);
+    REQUIRE((L"application/json" == result->GetResultContentType()));
+
+    STRING respContent = reader->ToString();
+    // This CRS string should be in the GeoJSON
+    REQUIRE(respContent.find(L"urn:ogc:def:crs:OGC:1.3:CRS84") != STRING::npos);
+}
+
+TEST_CASE("Wfs_GetFeature_GeoJSON_WebMercator", "[Wfs]")
+{
+    STRING uri = L"";
+    Ptr<MgHttpRequest> req = new MgHttpRequest(uri);
+    Ptr<MgHttpRequestParam> param = req->GetRequestParam();
+
+    MgConfiguration* cfg = MgConfiguration::GetInstance();
+    STRING pwd;
+    cfg->GetStringValue(MgConfigProperties::OgcPropertiesSection, MgConfigProperties::WfsPassword, pwd, L"");
+
+    param->AddParameter(L"USERNAME", MgUser::WfsUser);
+    param->AddParameter(L"PASSWORD", pwd);
+
+    param->AddParameter(L"SERVICE", L"WFS");
+    param->AddParameter(L"REQUEST", L"GETFEATURE");
+    param->AddParameter(L"VERSION", L"1.1.0");
+    param->AddParameter(L"TYPENAME", L"ns55197509:VotingDistricts");
+    param->AddParameter(L"MAXFEATURES", L"10");
+    param->AddParameter(L"SRSNAME", L"EPSG:3857");
+    param->AddParameter(L"OUTPUTFORMAT", L"application/json");
+
+    Ptr<MgHttpResponse> resp = req->Execute();
+    Ptr<MgHttpResult> result = resp->GetResult();
+
+    STRING err = result->GetDetailedErrorMessage();
+    REQUIRE(err.empty());
+
+    Ptr<MgByteReader> reader = dynamic_cast<MgByteReader*>(result->GetResultObject());
+    REQUIRE(reader.p != nullptr);
+    REQUIRE((L"application/json" == result->GetResultContentType()));
+
+    STRING respContent = reader->ToString();
+    // This CRS string should be in the GeoJSON
+    REQUIRE(respContent.find(L"urn:ogc:def:crs:EPSG::3857") != STRING::npos);
+}
+
+TEST_CASE("Wms_GetMap_ViewerRepresentation", "[Wms]")
+{
+    STRING uri = L"";
+    Ptr<MgHttpRequest> req = new MgHttpRequest(uri);
+    Ptr<MgHttpRequestParam> param = req->GetRequestParam();
+
+    MgConfiguration* cfg = MgConfiguration::GetInstance();
+    STRING pwd;
+    cfg->GetStringValue(MgConfigProperties::OgcPropertiesSection, MgConfigProperties::WmsPassword, pwd, L"");
+
+    param->AddParameter(L"USERNAME", MgUser::WmsUser);
+    param->AddParameter(L"PASSWORD", pwd);
+
+    param->AddParameter(L"SERVICE", L"WMS");
+    param->AddParameter(L"REQUEST", L"GETMAP");
+    param->AddParameter(L"VERSION", L"1.3.0");
+    param->AddParameter(L"LAYERS", L"Samples/Sheboygan/Layers/Parcels");
+    param->AddParameter(L"BBOX", L"43.73822699224595,-87.7398334220901,43.75068777906662,-87.72068284176505");
+    param->AddParameter(L"SRS", L"EPSG:4326");
+    param->AddParameter(L"WIDTH", L"796");
+    param->AddParameter(L"HEIGHT", L"717");
+    param->AddParameter(L"BGCOLOR", L"0x0000FF");
+    param->AddParameter(L"TRANSPARENT", L"FALSE");
+    param->AddParameter(L"FORMAT", L"application/openlayers");
+
+    Ptr<MgHttpResponse> resp = req->Execute();
+    Ptr<MgHttpResult> result = resp->GetResult();
+
+    STRING err = result->GetDetailedErrorMessage();
+    REQUIRE(err.empty());
+
+    Ptr<MgByteReader> reader = dynamic_cast<MgByteReader*>(result->GetResultObject());
+    REQUIRE(reader.p != nullptr);
+    REQUIRE((L"text/html" == result->GetResultContentType()));
+}
\ No newline at end of file

Deleted: trunk/MgDev/Web/src/WebTestRunner/OgcWfsTest.cpp
===================================================================
--- trunk/MgDev/Web/src/WebTestRunner/OgcWfsTest.cpp	2022-11-25 14:02:59 UTC (rev 10020)
+++ trunk/MgDev/Web/src/WebTestRunner/OgcWfsTest.cpp	2022-12-13 09:05:54 UTC (rev 10021)
@@ -1,45 +0,0 @@
-//
-//  Copyright (C) 2004-2020 by Autodesk, Inc.
-//
-//  This library is free software; you can redistribute it and/or
-//  modify it under the terms of version 2.1 of the GNU Lesser
-//  General Public License as published by the Free Software Foundation.
-//
-//  This library 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
-//  Lesser General Public License for more details.
-//
-//  You should have received a copy of the GNU Lesser General Public
-//  License along with this library; if not, write to the Free Software
-//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
-//
-
-#include "catch.hpp"
-#include "MapGuideCommon.h"
-#include "HttpHandler.h"
-
-TEST_CASE("GetWfsFeature_ViewerRepresentation", "[Wfs]")
-{
-    STRING uri = L"";
-    Ptr<MgHttpRequest> req = new MgHttpRequest(uri);
-    Ptr<MgHttpRequestParam> param = req->GetRequestParam();
-
-    param->AddParameter(L"SERVICE", L"WFS");
-    param->AddParameter(L"REQUEST", L"GETFEATURE");
-    param->AddParameter(L"VERSION", L"1.1.0");
-    param->AddParameter(L"MAXFEATURES", L"1000");
-    param->AddParameter(L"SRSNAME", L"EPSG:4326");
-    param->AddParameter(L"TYPENAME", L"ns55197509:VotingDistricts");
-    param->AddParameter(L"OUTPUTFORMAT", L"application/json");
-
-    Ptr<MgHttpResponse> resp = req->Execute();
-    Ptr<MgHttpResult> result = resp->GetResult();
-
-    STRING err = result->GetDetailedErrorMessage();
-    REQUIRE(err.empty());
-
-    Ptr<MgByteReader> reader = dynamic_cast<MgByteReader*>(result->GetResultObject());
-    REQUIRE(reader.p != nullptr);
-    REQUIRE((L"text/html" == result->GetResultContentType()));
-}
\ No newline at end of file

Modified: trunk/MgDev/Web/src/WebTestRunner/WebTestRunner.vcxproj
===================================================================
--- trunk/MgDev/Web/src/WebTestRunner/WebTestRunner.vcxproj	2022-11-25 14:02:59 UTC (rev 10020)
+++ trunk/MgDev/Web/src/WebTestRunner/WebTestRunner.vcxproj	2022-12-13 09:05:54 UTC (rev 10021)
@@ -119,7 +119,8 @@
       <Command>IF NOT EXIST "$(TargetDir)\Resources" mkdir "$(TargetDir)\Resources"
 xcopy /r /d /y "$(ProjectDir)..\..\..\Common\MapGuideCommon\Resources\mapguide_en.res" "$(TargetDir)\Resources"
 xcopy /r /d /y "$(SolutionDir)webconfig.ini" "$(TargetDir)"
-xcopy /s /y /i "$(ProjectDir)TestData" "$(TargetDir)TestData"</Command>
+xcopy /s /y /i "$(ProjectDir)TestData" "$(TargetDir)TestData"
+copy /y "$(ProjectDir)..\..\..\UnitTest\TestData\FeatureService\SDF\*.*" "$(TargetDir)TestData"</Command>
     </PostBuildEvent>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -150,7 +151,8 @@
       <Command>IF NOT EXIST "$(TargetDir)\Resources" mkdir "$(TargetDir)\Resources"
 xcopy /r /d /y "$(ProjectDir)..\..\..\Common\MapGuideCommon\Resources\mapguide_en.res" "$(TargetDir)\Resources"
 xcopy /r /d /y "$(SolutionDir)webconfig.ini" "$(TargetDir)"
-xcopy /s /y /i "$(ProjectDir)TestData" "$(TargetDir)TestData"</Command>
+xcopy /s /y /i "$(ProjectDir)TestData" "$(TargetDir)TestData"
+copy /y "$(ProjectDir)..\..\..\UnitTest\TestData\FeatureService\SDF\*.*" "$(TargetDir)TestData"</Command>
     </PostBuildEvent>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -182,7 +184,8 @@
       <Command>IF NOT EXIST "$(TargetDir)\Resources" mkdir "$(TargetDir)\Resources"
 xcopy /r /d /y "$(ProjectDir)..\..\..\Common\MapGuideCommon\Resources\mapguide_en.res" "$(TargetDir)\Resources"
 xcopy /r /d /y "$(SolutionDir)webconfig.ini" "$(TargetDir)"
-xcopy /s /y /i "$(ProjectDir)TestData" "$(TargetDir)TestData"</Command>
+xcopy /s /y /i "$(ProjectDir)TestData" "$(TargetDir)TestData"
+copy /y "$(ProjectDir)..\..\..\UnitTest\TestData\FeatureService\SDF\*.*" "$(TargetDir)TestData"</Command>
     </PostBuildEvent>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -214,12 +217,14 @@
       <Command>IF NOT EXIST "$(TargetDir)\Resources" mkdir "$(TargetDir)\Resources"
 xcopy /r /d /y "$(ProjectDir)..\..\..\Common\MapGuideCommon\Resources\mapguide_en.res" "$(TargetDir)\Resources"
 xcopy /r /d /y "$(SolutionDir)webconfig.ini" "$(TargetDir)"
-xcopy /s /y /i "$(ProjectDir)TestData" "$(TargetDir)TestData"</Command>
+xcopy /s /y /i "$(ProjectDir)TestData" "$(TargetDir)TestData"
+copy /y "$(ProjectDir)..\..\..\UnitTest\TestData\FeatureService\SDF\*.*" "$(TargetDir)TestData"</Command>
     </PostBuildEvent>
   </ItemDefinitionGroup>
   <ItemGroup>
+    <ClCompile Include="GeoJsonTest.cpp" />
     <ClCompile Include="main.cpp" />
-    <ClCompile Include="OgcWfsTest.cpp" />
+    <ClCompile Include="OgcTest.cpp" />
     <ClCompile Include="TestListener.cpp" />
     <ClCompile Include="WebLayoutTest.cpp" />
   </ItemGroup>

Modified: trunk/MgDev/Web/src/WebTestRunner/WebTestRunner.vcxproj.filters
===================================================================
--- trunk/MgDev/Web/src/WebTestRunner/WebTestRunner.vcxproj.filters	2022-11-25 14:02:59 UTC (rev 10020)
+++ trunk/MgDev/Web/src/WebTestRunner/WebTestRunner.vcxproj.filters	2022-12-13 09:05:54 UTC (rev 10021)
@@ -21,12 +21,15 @@
     <ClCompile Include="TestListener.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="OgcWfsTest.cpp">
+    <ClCompile Include="WebLayoutTest.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="WebLayoutTest.cpp">
+    <ClCompile Include="GeoJsonTest.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="OgcTest.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="resource.h">



More information about the mapguide-commits mailing list