[mapguide-commits] r10256 - in branches/4.0/MgDev: Common/Renderers Common/Stylization Server/src/UnitTesting

svn_mapguide at osgeo.org svn_mapguide at osgeo.org
Mon Nov 2 21:32:44 PST 2026


Author: jng
Date: 2026-11-02 21:32:43 -0800 (Mon, 02 Nov 2026)
New Revision: 10256

Modified:
   branches/4.0/MgDev/Common/Renderers/KmlRenderer.cpp
   branches/4.0/MgDev/Common/Renderers/KmlRenderer.h
   branches/4.0/MgDev/Common/Stylization/Renderer.h
   branches/4.0/MgDev/Common/Stylization/StylizationEngine.cpp
   branches/4.0/MgDev/Common/Stylization/StylizationEngine.h
   branches/4.0/MgDev/Server/src/UnitTesting/TestKmlService.cpp
   branches/4.0/MgDev/Server/src/UnitTesting/TestServiceFactory.cpp
Log:
Fix excessive KML placemarks being rendered under composite symbolization. Fixes #2488

Modified: branches/4.0/MgDev/Common/Renderers/KmlRenderer.cpp
===================================================================
--- branches/4.0/MgDev/Common/Renderers/KmlRenderer.cpp	2026-11-03 05:06:46 UTC (rev 10255)
+++ branches/4.0/MgDev/Common/Renderers/KmlRenderer.cpp	2026-11-03 05:32:43 UTC (rev 10256)
@@ -39,7 +39,7 @@
     m_mainContent(kmlContent),
     m_kmlContent(kmlContent),
     m_styleContent(NULL),
-    m_featureCount(0),
+    m_placemarkOpen(false),
     m_mapInfo(NULL),
     m_layerInfo(NULL),
     m_featureClassInfo(NULL),
@@ -72,7 +72,7 @@
                            double        /*metersPerUnit*/,
                            CSysTransformer* /*xformToLL*/)
 {
-    m_featureCount = 0;
+    m_placemarkOpen = false;
 
     // remember the map info
     m_mapInfo = mapInfo;
@@ -89,7 +89,7 @@
 void KmlRenderer::StartLayer(RS_LayerUIInfo*      layerInfo,
                              RS_FeatureClassInfo* classInfo)
 {
-    m_featureCount = 0;
+    m_placemarkOpen = false;
     m_layerInfo = layerInfo;
     m_featureClassInfo = classInfo;
     ClearThemes();
@@ -100,11 +100,7 @@
 void KmlRenderer::EndLayer()
 {
     //close the very last feature
-    if (m_featureCount > 0)
-    {
-        //end last feature
-        EndPlacemark();
-    }
+    EndPlacemark();
     m_kmlContent = m_mainContent;
 
     //write out the features for each theme
@@ -128,6 +124,9 @@
 
 void KmlRenderer::EndPlacemark()
 {
+    if (!m_placemarkOpen)
+        return;
+
     //a placemark has one name at most, and an empty one shows as a blank entry
     //next to the real name in Google Earth, so only write it when there is one
     if (!m_placemarkName.empty())
@@ -138,11 +137,12 @@
         m_placemarkName.clear();
     }
     m_kmlContent->WriteString("</Placemark>");
+    m_placemarkOpen = false;
 }
 
 
 void KmlRenderer::StartFeature(RS_FeatureReader* /*feature*/,
-                               bool /*initialPass*/,
+                               bool initialPass,
                                const RS_String* tooltip,
                                const RS_String* url,
                                const RS_String* theme,
@@ -150,56 +150,64 @@
                                double zExtrusion,
                                RS_ElevationType zOffsetType)
 {
-    if (m_featureCount > 0)
+    KmlContent* content = m_mainContent;
+    if (theme != NULL && !theme->empty())
     {
-        //end last feature
-        EndPlacemark();
-    }
-    if (theme == NULL || theme->empty())
-    {
-        m_kmlContent = m_mainContent;
-    }
-    else
-    {
         ThemeMap::iterator iter = m_themeMap.find(*theme);
         if (iter != m_themeMap.end())
         {
-            m_kmlContent = (*iter).second;
+            content = (*iter).second;
         }
         else
         {
-            m_kmlContent = new KmlContent();
-            m_themeMap[*theme] = m_kmlContent;
+            content = new KmlContent();
+            m_themeMap[*theme] = content;
         }
     }
-    m_placemarkName.clear();
-    m_kmlContent->WriteString("<Placemark>");
-    if ((tooltip != NULL && tooltip->length() > 0) ||
-        (url != NULL && url->length() > 0))
+
+    //a feature is stylized once per composite type style, so this is called once
+    //per style of the same feature and only the first of those passes is the
+    //initial one. The placemark belongs to the feature, not to the style pass, so
+    //the remaining passes add to the placemark the initial pass opened. A pass
+    //that moves the feature to another theme cannot continue it, because a
+    //placemark cannot span the folders the themes are written into.
+    if (m_placemarkOpen && (initialPass || content != m_kmlContent))
     {
-        m_kmlContent->WriteString("<description>");
-        m_kmlContent->WriteString("<![CDATA[", false);
-        if (tooltip != NULL && tooltip->length() > 0)
+        EndPlacemark();
+    }
+    m_kmlContent = content;
+
+    if (!m_placemarkOpen)
+    {
+        m_placemarkName.clear();
+        m_kmlContent->WriteString("<Placemark>");
+        m_placemarkOpen = true;
+        if ((tooltip != NULL && tooltip->length() > 0) ||
+            (url != NULL && url->length() > 0))
         {
-            //replace line breaks
-            RS_String newTT(*tooltip);
-            RS_String::size_type lb = newTT.find(L"\\n");
-            while (lb != RS_String::npos)
+            m_kmlContent->WriteString("<description>");
+            m_kmlContent->WriteString("<![CDATA[", false);
+            if (tooltip != NULL && tooltip->length() > 0)
             {
-                newTT = newTT.replace(lb, 2, L"<br/>");
-                lb = newTT.find(L"\\n", lb);
+                //replace line breaks
+                RS_String newTT(*tooltip);
+                RS_String::size_type lb = newTT.find(L"\\n");
+                while (lb != RS_String::npos)
+                {
+                    newTT = newTT.replace(lb, 2, L"<br/>");
+                    lb = newTT.find(L"\\n", lb);
+                }
+                m_kmlContent->WriteString(newTT, false);
+                m_kmlContent->WriteString("<br/>", false);
             }
-            m_kmlContent->WriteString(newTT, false);
-            m_kmlContent->WriteString("<br/>", false);
+            if (url != NULL && url->length() > 0)
+            {
+                m_kmlContent->WriteString(*url, false);
+            }
+            m_kmlContent->WriteString("]]>");
+            m_kmlContent->WriteString("</description>");
         }
-        if (url != NULL && url->length() > 0)
-        {
-            m_kmlContent->WriteString(*url, false);
-        }
-        m_kmlContent->WriteString("]]>");
-        m_kmlContent->WriteString("</description>");
     }
-    m_featureCount++;
 
     // Elevation / Extrusion settings
     m_elevation = zExtrusion + zOffset;
@@ -507,6 +515,16 @@
 }
 
 
+bool KmlRenderer::RequiresCompositeLineStyleSeparation()
+{
+    //separating a rule's line styles into one styling pass each is what the
+    //screen renderers need, but it would draw a feature more than once and so
+    //give it a placemark per pass - a placemark holds all of a rule's line
+    //styles, so they can be stylized together (ticket #2488)
+    return false;
+}
+
+
 bool KmlRenderer::SupportsZ()
 {
     // although KmlRenderer supports elevation, it ignores Z values

Modified: branches/4.0/MgDev/Common/Renderers/KmlRenderer.h
===================================================================
--- branches/4.0/MgDev/Common/Renderers/KmlRenderer.h	2026-11-03 05:06:46 UTC (rev 10255)
+++ branches/4.0/MgDev/Common/Renderers/KmlRenderer.h	2026-11-03 05:32:43 UTC (rev 10256)
@@ -120,6 +120,8 @@
 
     virtual bool RequiresLabelClipping();
 
+    virtual bool RequiresCompositeLineStyleSeparation();
+
     virtual bool SupportsZ();
 
     ////////////////////////////////////////////////
@@ -183,7 +185,9 @@
     KmlContent* m_kmlContent;
     KmlContent* m_mainContent;
     KmlContent* m_styleContent;
-    int m_featureCount;
+    // true while a <Placemark> has been written and not yet closed. A feature's
+    // style passes share the placemark its initial pass opened.
+    bool m_placemarkOpen;
     RS_MapUIInfo* m_mapInfo;
     RS_LayerUIInfo* m_layerInfo;
     RS_FeatureClassInfo* m_featureClassInfo;

Modified: branches/4.0/MgDev/Common/Stylization/Renderer.h
===================================================================
--- branches/4.0/MgDev/Common/Stylization/Renderer.h	2026-11-03 05:06:46 UTC (rev 10255)
+++ branches/4.0/MgDev/Common/Stylization/Renderer.h	2026-11-03 05:32:43 UTC (rev 10256)
@@ -76,7 +76,11 @@
 
     ///<summary>
     /// Indicates the renderer should process all subsequent geometry
-    /// as part of a single logical feature.
+    /// as part of a single logical feature. When a feature is stylized by
+    /// several composite type styles, this is called once per style and
+    /// initialPass is true only for the first of those calls, which is
+    /// where a renderer that groups a feature's output (the KML
+    /// placemark, for example) opens its group.
     ///</summary>
     virtual void StartFeature(RS_FeatureReader* feature,
                               bool              initialPass,

Modified: branches/4.0/MgDev/Common/Stylization/StylizationEngine.cpp
===================================================================
--- branches/4.0/MgDev/Common/Stylization/StylizationEngine.cpp	2026-11-03 05:06:46 UTC (rev 10255)
+++ branches/4.0/MgDev/Common/Stylization/StylizationEngine.cpp	2026-11-03 05:32:43 UTC (rev 10256)
@@ -166,12 +166,20 @@
             }
 
             // stylize once for each composite type style
+            bool featureStarted = false;
             for (size_t i=0; i<numTypeStyles; ++i)
             {
-                bool initialPass = (i == 0 && instanceRenderingPass == 0 && symbolRenderingPass == 0);
-                Stylize(reader, exec, lb, compTypeStyles[i], &seTip, &seUrl, NULL,
-                        initialPass, instanceRenderingPass, symbolRenderingPass,
-                        nextInstanceRenderingPass, nextSymbolRenderingPass);
+                // The initial pass is the one that opens the feature for the renderer,
+                // which is the first style whose rules match - a style whose rules match
+                // nothing never reaches the renderer at all, and the style passes that
+                // follow the initial one belong to the same feature.
+                bool initialPass = !featureStarted;
+                if (Stylize(reader, exec, lb, compTypeStyles[i], &seTip, &seUrl, NULL,
+                            initialPass, instanceRenderingPass, symbolRenderingPass,
+                            nextInstanceRenderingPass, nextSymbolRenderingPass))
+                {
+                    featureStarted = true;
+                }
             }
 
             // free geometry when done stylizing
@@ -751,7 +759,7 @@
 }
 
 
-void StylizationEngine::Stylize(RS_FeatureReader* reader,
+bool StylizationEngine::Stylize(RS_FeatureReader* reader,
                                 FdoExpressionEngine* exec,
                                 LineBuffer* geometry,
                                 CompositeTypeStyle* style,
@@ -824,8 +832,9 @@
         }
     }
 
+    // no rule matches, so the renderer never hears about this feature
     if (rule == NULL)
-        return;
+        return false;
 
     // we found a valid rule - send a StartFeature notification
     RS_String rs_tip, rs_url;
@@ -840,7 +849,7 @@
     // up with no symbols - we're done in that case.
     std::vector<SE_SymbolInstance*>* symbolInstances = &rule->symbolInstances;
     if (symbolInstances->size() == 0)
-        return;
+        return true;
 
     double mm2sud = m_serenderer->GetScreenUnitsPerMillimeterDevice();
     double mm2suw = m_serenderer->GetScreenUnitsPerMillimeterWorld();
@@ -1103,7 +1112,7 @@
             // if the clipped buffer is NULL (completely clipped) just move on to
             // the next feature
             if (!lbc)
-                return;
+                return true;
 
             // otherwise continue processing with the clipped buffer
             lb = lbc;
@@ -1250,6 +1259,8 @@
     // free clipped line buffer if the geometry was clipped
     if (spClipLB.get())
         LineBufferPool::FreeLineBuffer(m_pool, spClipLB.release());
+
+    return true;
 }
 
 

Modified: branches/4.0/MgDev/Common/Stylization/StylizationEngine.h
===================================================================
--- branches/4.0/MgDev/Common/Stylization/StylizationEngine.h	2026-11-03 05:06:46 UTC (rev 10255)
+++ branches/4.0/MgDev/Common/Stylization/StylizationEngine.h	2026-11-03 05:32:43 UTC (rev 10256)
@@ -78,7 +78,9 @@
                           int saveHeight);
 
     // Stylizes the current feature on the reader using the supplied composite type style.
-    void Stylize(RS_FeatureReader* reader,
+    // Returns true if the renderer was given the feature - the style's rules can match
+    // nothing, in which case no StartFeature() is issued for it.
+    bool Stylize(RS_FeatureReader* reader,
                  FdoExpressionEngine* exec,
                  LineBuffer* geometry,
                  CompositeTypeStyle* style,

Modified: branches/4.0/MgDev/Server/src/UnitTesting/TestKmlService.cpp
===================================================================
--- branches/4.0/MgDev/Server/src/UnitTesting/TestKmlService.cpp	2026-11-03 05:06:46 UTC (rev 10255)
+++ branches/4.0/MgDev/Server/src/UnitTesting/TestKmlService.cpp	2026-11-03 05:32:43 UTC (rev 10256)
@@ -484,3 +484,220 @@
     REQUIRE(CountOccurrences(placemark, "<name>") == 1);
     REQUIRE(placemark.find("<name><![CDATA[BlockSymbol]]></name>") != std::string::npos);
 }
+
+//////////////////////////////////////////////////////////////////////////////
+// Kml renderer placemark grouping across composite style passes (ticket #2488)
+//
+// StylizationEngine::StylizeVectorLayer() stylizes a feature once per composite
+// type style, so StartFeature() is called several times for the same feature and
+// only the first of those calls is the initial pass. A placemark belongs to the
+// feature, not to the style pass, so all of a feature's style passes have to land
+// in one <Placemark> - otherwise GetFeaturesKml() reports one placemark per style.
+
+namespace
+{
+    // Everything in the KML that is not inside a <Placemark> element
+    std::string GetContentOutsidePlacemarks(const std::string& kml)
+    {
+        static const std::string startTag("<Placemark>");
+        static const std::string endTag("</Placemark>");
+
+        std::string content;
+        std::string::size_type pos = 0;
+        while (true)
+        {
+            std::string::size_type start = kml.find(startTag, pos);
+            std::string::size_type end = (start == std::string::npos)? std::string::npos : kml.find(endTag, start);
+            if (end == std::string::npos)
+            {
+                content.append(kml, pos, std::string::npos);
+                break;
+            }
+            content.append(kml, pos, start - pos);
+            pos = end + endTag.length();
+        }
+        return content;
+    }
+
+    // Renders one point feature, calling StartFeature() once per composite type
+    // style pass, in the order StylizationEngine does it. A feature whose leading
+    // styles match no rule has no call for those styles, so initialPass marks the
+    // first pass it actually reaches the renderer for.
+    //
+    // ProcessMarker() rather than ProcessPolyline() because it is the one the
+    // renderer exports: the geometry adapters reach the other Process* methods
+    // through the vtable, so only ProcessMarker() is linkable from this module
+    // when KmlRenderer lives in its own DLL (the Windows build).
+    void StylizePointFeature(KmlRenderer&     renderer,
+                             int              stylePasses,
+                             bool             initialPassOnFirst,
+                             double           y,
+                             const RS_String* theme = NULL)
+    {
+        LineBuffer lb(2);
+        lb.MoveTo(-87.70, y);
+
+        RS_MarkerDef mdef;
+
+        for (int pass = 0; pass < stylePasses; ++pass)
+        {
+            renderer.StartFeature(NULL, initialPassOnFirst && pass == 0, NULL, NULL, theme, 0.0, 0.0, RS_ElevationType_RelativeToGround);
+            renderer.ProcessMarker(&lb, mdef, true, NULL);
+        }
+    }
+
+    void StartRailLayer(KmlRenderer& renderer)
+    {
+        RS_UIGraphic graphic(NULL, 0, L"");
+        RS_LayerUIInfo layerInfo(L"Rail", L"Library://UnitTests/Layers/MultiCTS.LayerDefinition",
+                                 L"", true, true, false, L"", L"", true, true, 0.0, graphic);
+        RS_FeatureClassInfo classInfo(L"Rail", L"Library://UnitTests/Data/Rail.FeatureSource", L"SHPGEOM");
+        renderer.StartLayer(&layerInfo, &classInfo);
+    }
+}
+
+TEST_CASE("KmlCompositeStyleWritesOnePlacemark", "[KmlService]")
+{
+    KmlContent kmlContent;
+    RS_Bounds bounds(-87.8, 43.7, -87.6, 43.8);
+    KmlRenderer renderer(&kmlContent, bounds, 10000.0, 96.0, 1.0, 0);
+    StartRailLayer(renderer);
+
+    // one feature, stylized by three composite type styles
+    StylizePointFeature(renderer, 3, true, 43.75);
+
+    renderer.EndLayer();
+
+    std::string kml = kmlContent.GetString();
+    INFO(kml);
+    std::vector<std::string> placemarks = GetPlacemarks(kml);
+    REQUIRE(placemarks.size() == 1);
+    REQUIRE(CountOccurrences(placemarks.front(), "<Point>") == 3);
+    REQUIRE(GetContentOutsidePlacemarks(kml).find("<coordinates>") == std::string::npos);
+}
+
+TEST_CASE("KmlCompositeStyleKeepsFeaturesApart", "[KmlService]")
+{
+    KmlContent kmlContent;
+    RS_Bounds bounds(-87.8, 43.7, -87.6, 43.8);
+    KmlRenderer renderer(&kmlContent, bounds, 10000.0, 96.0, 1.0, 0);
+    StartRailLayer(renderer);
+
+    // two features, two composite type styles each
+    StylizePointFeature(renderer, 2, true, 43.75);
+    StylizePointFeature(renderer, 2, true, 43.78);
+
+    renderer.EndLayer();
+
+    std::string kml = kmlContent.GetString();
+    INFO(kml);
+    std::vector<std::string> placemarks = GetPlacemarks(kml);
+    REQUIRE(placemarks.size() == 2);
+    for (size_t i = 0; i < placemarks.size(); ++i)
+    {
+        // each feature's style passes stay in its own placemark
+        REQUIRE(CountOccurrences(placemarks[i], "<Point>") == 2);
+    }
+    REQUIRE(GetContentOutsidePlacemarks(kml).find("<coordinates>") == std::string::npos);
+}
+
+TEST_CASE("KmlCompositeStylePlacemarkDoesNotSpanThemes", "[KmlService]")
+{
+    KmlContent kmlContent;
+    RS_Bounds bounds(-87.8, 43.7, -87.6, 43.8);
+    KmlRenderer renderer(&kmlContent, bounds, 10000.0, 96.0, 1.0, 0);
+    StartRailLayer(renderer);
+
+    // a feature whose two composite type styles carry different legend labels:
+    // each theme is written into its own folder, and a placemark cannot be left
+    // open across folders, so the second theme starts a placemark of its own
+    RS_String themeA(L"Single Track");
+    RS_String themeB(L"Dual Track");
+    StylizePointFeature(renderer, 1, true, 43.75, &themeA);
+    StylizePointFeature(renderer, 1, false, 43.75, &themeB);
+
+    renderer.EndLayer();
+
+    std::string kml = kmlContent.GetString();
+    INFO(kml);
+    std::vector<std::string> placemarks = GetPlacemarks(kml);
+    REQUIRE(placemarks.size() == 2);
+    for (size_t i = 0; i < placemarks.size(); ++i)
+    {
+        REQUIRE(CountOccurrences(placemarks[i], "<Point>") == 1);
+    }
+    REQUIRE(GetContentOutsidePlacemarks(kml).find("<coordinates>") == std::string::npos);
+}
+
+TEST_CASE("GetFeaturesKmlCompositeStylePerFeature", "[KmlService]")
+{
+    try
+    {
+        Ptr<MgKmlService> m_svcKml = TestServiceFactory::CreateKmlService();
+        Ptr<MgResourceService> m_svcResource = TestServiceFactory::CreateResourceService();
+
+        Ptr<MgEnvelope> extents = new MgEnvelope(-87.764986990962839, 43.691398128787782,
+                                                 -87.695521510899724, 43.797520000480347);
+        STRING agentUri = L"http://myserver/mapguide/mapagent/mapagent.fcgi";
+        STRING format = L"KML";
+
+        try
+        {
+            // MultiCTS.LayerDefinition carries the Rail features, the same ones
+            // Rail.LayerDefinition draws, but with three composite type styles - so
+            // each feature is stylized three times. The KML must still carry one
+            // placemark per feature, and therefore as many placemarks as the layer
+            // with a single style produces over the same features (ticket #2488).
+            Ptr<MgResourceIdentifier> compositeLdf = new MgResourceIdentifier(L"Library://UnitTests/Layers/MultiCTS.LayerDefinition");
+            Ptr<MgLayer> compositeLayer = new MgLayer(compositeLdf, m_svcResource);
+
+            Ptr<MgResourceIdentifier> lineLdf = new MgResourceIdentifier(L"Library://UnitTests/Layers/Rail.LayerDefinition");
+            Ptr<MgLayer> lineLayer = new MgLayer(lineLdf, m_svcResource);
+
+            Ptr<MgByteReader> compositeReader = m_svcKml->GetFeaturesKml(compositeLayer, extents, 796, 717, 96.0, 0, agentUri, format);
+            Ptr<MgByteReader> lineReader = m_svcKml->GetFeaturesKml(lineLayer, extents, 796, 717, 96.0, 0, agentUri, format);
+
+            // the KML mime type is not one MgByteSink::ToStringUtf8() will read
+            compositeReader->GetByteSource()->SetMimeType(L"text/xml");
+            lineReader->GetByteSource()->SetMimeType(L"text/xml");
+            std::string compositeKml = MgUtil::WideCharToMultiByte(compositeReader->ToString());
+            std::string lineKml = MgUtil::WideCharToMultiByte(lineReader->ToString());
+
+            int compositePlacemarks = CountOccurrences(compositeKml, "<Placemark>");
+            int linePlacemarks = CountOccurrences(lineKml, "<Placemark>");
+
+            INFO(compositeKml);
+            INFO(lineKml);
+            REQUIRE(linePlacemarks > 0);
+            REQUIRE(compositePlacemarks == linePlacemarks);
+        }
+        catch (MgException* e)
+        {
+            if (e->GetExceptionCode() == MgExceptionCodes::MgConnectionFailedException)
+            {
+                SAFE_RELEASE(e);
+                ACE_DEBUG((LM_INFO, ACE_TEXT("\nTestCase_GetFeaturesKmlCompositeStylePerFeature skipped because unable to connect to test server.\n")));
+
+                // We need to update the mapguide server status so that it can be used again because we have used the
+                // same exception class for servers outside the one actually executing this code.
+                MgSiteManager* siteManager = MgSiteManager::GetInstance();
+                Ptr<MgSiteInfo> siteInfo = siteManager->GetSiteInfo(0);
+                siteInfo->SetStatus(MgSiteInfo::Ok);
+            }
+            else
+            {
+                e->Raise();
+            }
+        }
+    }
+    catch(MgException* e)
+    {
+        STRING message = e->GetDetails(TestServiceFactory::TEST_LOCALE);
+        SAFE_RELEASE(e);
+        FAIL(MG_WCHAR_TO_CHAR(message.c_str()));
+    }
+    catch(...)
+    {
+        throw;
+    }
+}

Modified: branches/4.0/MgDev/Server/src/UnitTesting/TestServiceFactory.cpp
===================================================================
--- branches/4.0/MgDev/Server/src/UnitTesting/TestServiceFactory.cpp	2026-11-03 05:06:46 UTC (rev 10255)
+++ branches/4.0/MgDev/Server/src/UnitTesting/TestServiceFactory.cpp	2026-11-03 05:32:43 UTC (rev 10256)
@@ -465,6 +465,13 @@
                 Ptr<MgByteReader> ldfrdr3 = ldfsrc3->GetReader();
                 svcResource->SetResource(ldfres3, ldfrdr3, nullptr);
 
+                //the features of Rail.LayerDefinition, but with several composite type
+                //styles, so each feature is stylized once per style (ticket #2488)
+                Ptr<MgResourceIdentifier> ldfres4 = new MgResourceIdentifier(L"Library://UnitTests/Layers/MultiCTS.LayerDefinition");
+                Ptr<MgByteSource> ldfsrc4 = new MgByteSource(L"../UnitTestFiles/UT_MultiCTS.ldf", false);
+                Ptr<MgByteReader> ldfrdr4 = ldfsrc4->GetReader();
+                svcResource->SetResource(ldfres4, ldfrdr4, nullptr);
+
                 //publish the feature sources
                 Ptr<MgResourceIdentifier> fsres1 = new MgResourceIdentifier(L"Library://UnitTests/Data/HydrographicPolygons.FeatureSource");
                 Ptr<MgByteSource> fssrc1 = new MgByteSource(L"../UnitTestFiles/UT_HydrographicPolygons.fs", false);
@@ -1735,6 +1742,9 @@
                 Ptr<MgResourceIdentifier> ldfres3 = new MgResourceIdentifier(L"Library://UnitTests/Layers/Rail.LayerDefinition");
                 svcResource->DeleteResource(ldfres3);
 
+                Ptr<MgResourceIdentifier> ldfres4 = new MgResourceIdentifier(L"Library://UnitTests/Layers/MultiCTS.LayerDefinition");
+                svcResource->DeleteResource(ldfres4);
+
                 //delete the feature sources
                 Ptr<MgResourceIdentifier> fsres1 = new MgResourceIdentifier(L"Library://UnitTests/Data/HydrographicPolygons.FeatureSource");
                 svcResource->DeleteResource(fsres1);



More information about the mapguide-commits mailing list