[mapguide-commits] r9510 - in trunk/MgDev: . Common/Foundation/Data Common/Foundation/System Common/MapGuideCommon Common/MapGuideCommon/MapLayer Common/MapGuideCommon/Resources Common/MapGuideCommon/Services Common/MapGuideCommon/System Common/Renderers Server/src/PostBuild Server/src/Services/Mapping Server/src/Services/Rendering Server/src/Services/Tile Server/src/UnitTesting UnitTest/TestData/TileService Web/src/DotNetUnmanagedApi/MapGuideCommon Web/src/MapGuideApi

svn_mapguide at osgeo.org svn_mapguide at osgeo.org
Mon May 27 05:24:20 PDT 2019


Author: jng
Date: 2019-05-27 05:24:20 -0700 (Mon, 27 May 2019)
New Revision: 9510

Added:
   trunk/MgDev/Common/MapGuideCommon/Services/Metatile.cpp
   trunk/MgDev/Common/MapGuideCommon/Services/Metatile.h
   trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatile.cpp
   trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatile.h
   trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatileXYZ.cpp
   trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatileXYZ.h
   trunk/MgDev/Server/src/Services/Rendering/OpRenderTileFromMetatile.cpp
   trunk/MgDev/Server/src/Services/Rendering/OpRenderTileFromMetatile.h
   trunk/MgDev/Server/src/Services/Tile/MetatileLockUtil.cpp
   trunk/MgDev/Server/src/Services/Tile/MetatileLockUtil.h
   trunk/MgDev/UnitTest/TestData/TileService/UT_BaseMap_Metatiled.tsd
   trunk/MgDev/UnitTest/TestData/TileService/UT_BaseMap_OrigSize.tsd
   trunk/MgDev/UnitTest/TestData/TileService/UT_XYZ_Metatiled.tsd
Modified:
   trunk/MgDev/
   trunk/MgDev/Common/Foundation/Data/MimeType.cpp
   trunk/MgDev/Common/Foundation/Data/MimeType.h
   trunk/MgDev/Common/Foundation/System/ByteSourceMemoryImpl.cpp
   trunk/MgDev/Common/Foundation/System/ByteSourceMemoryImpl.h
   trunk/MgDev/Common/MapGuideCommon/MapGuideCommon.h
   trunk/MgDev/Common/MapGuideCommon/MapGuideCommon.vcxproj
   trunk/MgDev/Common/MapGuideCommon/MapGuideCommon.vcxproj.filters
   trunk/MgDev/Common/MapGuideCommon/MapGuideCommonBuild.cpp
   trunk/MgDev/Common/MapGuideCommon/MapLayer/Map.cpp
   trunk/MgDev/Common/MapGuideCommon/MapLayer/Map.h
   trunk/MgDev/Common/MapGuideCommon/Resources/mapguide_en.res
   trunk/MgDev/Common/MapGuideCommon/Services/ImageFormats.cpp
   trunk/MgDev/Common/MapGuideCommon/Services/ImageFormats.h
   trunk/MgDev/Common/MapGuideCommon/Services/ProxyRenderingService.cpp
   trunk/MgDev/Common/MapGuideCommon/Services/ProxyRenderingService.h
   trunk/MgDev/Common/MapGuideCommon/Services/RenderingDefs.h
   trunk/MgDev/Common/MapGuideCommon/Services/RenderingService.h
   trunk/MgDev/Common/MapGuideCommon/Services/TileDefs.h
   trunk/MgDev/Common/MapGuideCommon/System/MapGuideCommonClassId.h
   trunk/MgDev/Common/MapGuideCommon/System/MapGuideCommonFactory.cpp
   trunk/MgDev/Common/Renderers/AGGRenderer.cpp
   trunk/MgDev/Common/Renderers/AGGRenderer.h
   trunk/MgDev/Server/src/PostBuild/PostBuild.mak
   trunk/MgDev/Server/src/Services/Mapping/MappingUtil.h
   trunk/MgDev/Server/src/Services/Rendering/OpRenderTile.cpp
   trunk/MgDev/Server/src/Services/Rendering/OpRenderTileXYZ.cpp
   trunk/MgDev/Server/src/Services/Rendering/RenderingOperationFactory.cpp
   trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.cpp
   trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.h
   trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.vcxproj
   trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.vcxproj.filters
   trunk/MgDev/Server/src/Services/Rendering/ServerRenderingServiceBuild.cpp
   trunk/MgDev/Server/src/Services/Tile/ServerTileService.cpp
   trunk/MgDev/Server/src/Services/Tile/ServerTileService.h
   trunk/MgDev/Server/src/Services/Tile/ServerTileService.vcxproj
   trunk/MgDev/Server/src/Services/Tile/ServerTileService.vcxproj.filters
   trunk/MgDev/Server/src/Services/Tile/ServerTileServiceBuild.cpp
   trunk/MgDev/Server/src/Services/Tile/TileCacheDefault.cpp
   trunk/MgDev/Server/src/Services/Tile/TileCacheDefault.h
   trunk/MgDev/Server/src/Services/Tile/TileCacheDefaultProvider.cpp
   trunk/MgDev/Server/src/Services/Tile/TileCacheDefaultProvider.h
   trunk/MgDev/Server/src/Services/Tile/TileCacheXYZProvider.cpp
   trunk/MgDev/Server/src/Services/Tile/TileCacheXYZProvider.h
   trunk/MgDev/Server/src/UnitTesting/TestRenderingService.cpp
   trunk/MgDev/Server/src/UnitTesting/TestRenderingService.h
   trunk/MgDev/Server/src/UnitTesting/TestTileService.cpp
   trunk/MgDev/Server/src/UnitTesting/TestTileService.h
   trunk/MgDev/Web/src/DotNetUnmanagedApi/MapGuideCommon/MapGuideCommonApiGen.xml
   trunk/MgDev/Web/src/MapGuideApi/MapGuideApiGen.xml
   trunk/MgDev/cmake_linuxapt.sh
Log:
RFC 170: Merge tiling_v2 sandbox

Index: trunk/MgDev
===================================================================
--- trunk/MgDev	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev	2019-05-27 12:24:20 UTC (rev 9510)

Property changes on: trunk/MgDev
___________________________________________________________________
Modified: svn:mergeinfo
## -24,6 +24,7 ##
 /sandbox/jng/rfc155:8872-8884
 /sandbox/jng/simplify:8814-9141
 /sandbox/jng/tiling:8174-8208
+/sandbox/jng/tiling_v2:9490-9509
 /sandbox/jng/utfgrid:9179-9212
 /sandbox/jng/v30:8212-8227
 /sandbox/rfc94:5099-5163
Modified: trunk/MgDev/Common/Foundation/Data/MimeType.cpp
===================================================================
--- trunk/MgDev/Common/Foundation/Data/MimeType.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/Foundation/Data/MimeType.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -36,4 +36,4 @@
 const STRING MgMimeType::Json            = L"application/json";
 const STRING MgMimeType::Kml             = L"application/vnd.google-earth.kml+xml";
 const STRING MgMimeType::Kmz             = L"application/vnd.google-earth.kmz";
-
+const STRING MgMimeType::Meta            = L"application/metatile";

Modified: trunk/MgDev/Common/Foundation/Data/MimeType.h
===================================================================
--- trunk/MgDev/Common/Foundation/Data/MimeType.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/Foundation/Data/MimeType.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -83,6 +83,10 @@
     /// \brief
     /// application/vnd.google-earth.kmz
     static const STRING Kmz;      ///\if INTERNAL value("application/vnd.google-earth.kmz") \endif
+
+    /// \brief
+    /// application/metatile
+    static const STRING Meta;      ///\if INTERNAL value("application/metatile") \endif
 };
 /// \}
 

Modified: trunk/MgDev/Common/Foundation/System/ByteSourceMemoryImpl.cpp
===================================================================
--- trunk/MgDev/Common/Foundation/System/ByteSourceMemoryImpl.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/Foundation/System/ByteSourceMemoryImpl.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -99,3 +99,8 @@
 {
     return (SAFE_ADDREF((MgByte*)m_data));
 }
+
+MgByte* ByteSourceMemoryImpl::BytesNoAddRef()
+{
+    return (MgByte*)m_data;
+}
\ No newline at end of file

Modified: trunk/MgDev/Common/Foundation/System/ByteSourceMemoryImpl.h
===================================================================
--- trunk/MgDev/Common/Foundation/System/ByteSourceMemoryImpl.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/Foundation/System/ByteSourceMemoryImpl.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -112,6 +112,16 @@
     ///
     MgByte* Bytes();
 
+    ////////////////////////////////////////////////////////////////
+    /// \brief
+    /// Gets the pojnter the to internal MgByte object. The reference count
+    /// is not incremented
+    ///
+    /// \return
+    /// Internal MgByte
+    ///
+    MG_FOUNDATION_API MgByte* BytesNoAddRef();
+
 protected:
     Ptr<MgByte> m_data;
     INT32   m_pos;

Modified: trunk/MgDev/Common/MapGuideCommon/MapGuideCommon.h
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/MapGuideCommon.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/MapGuideCommon.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -145,6 +145,7 @@
 #include "Services/MapPlot.h"
 #include "Services/MapPlotCollection.h"
 #include "Services/MapPlotInstruction.h"
+#include "Services/Metatile.h"
 #include "Services/FeatureInformation.h"
 #include "Services/MappingDefs.h"
 #include "Services/MappingService.h"

Modified: trunk/MgDev/Common/MapGuideCommon/MapGuideCommon.vcxproj
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/MapGuideCommon.vcxproj	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/MapGuideCommon.vcxproj	2019-05-27 12:24:20 UTC (rev 9510)
@@ -684,6 +684,12 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="Services\Metatile.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="Services\PackageStatusInformation.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
@@ -1033,6 +1039,7 @@
     <ClInclude Include="Services\MapPlot.h" />
     <ClInclude Include="Services\MapPlotCollection.h" />
     <ClInclude Include="Services\MapPlotInstruction.h" />
+    <ClInclude Include="Services\Metatile.h" />
     <ClInclude Include="Services\PackageStatusInformation.h" />
     <ClInclude Include="Services\PlotSpecification.h" />
     <ClInclude Include="Services\PrintLayout.h" />

Modified: trunk/MgDev/Common/MapGuideCommon/MapGuideCommon.vcxproj.filters
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/MapGuideCommon.vcxproj.filters	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/MapGuideCommon.vcxproj.filters	2019-05-27 12:24:20 UTC (rev 9510)
@@ -400,6 +400,9 @@
     <ClCompile Include="Exception\UnsupportedTileProviderException.cpp">
       <Filter>Exception</Filter>
     </ClCompile>
+    <ClCompile Include="Services\Metatile.cpp">
+      <Filter>Services</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="MapLayer\Layer.h">
@@ -781,6 +784,9 @@
     <ClInclude Include="Exception\UnsupportedTileProviderException.h">
       <Filter>Exception</Filter>
     </ClInclude>
+    <ClInclude Include="Services\Metatile.h">
+      <Filter>Services</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="MapGuideCommon.rc" />

Modified: trunk/MgDev/Common/MapGuideCommon/MapGuideCommonBuild.cpp
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/MapGuideCommonBuild.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/MapGuideCommonBuild.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -96,6 +96,7 @@
 #include  "Services/MappingService.cpp"
 #include  "Services/MapPlot.cpp"
 #include  "Services/MapPlotCollection.cpp"
+#include  "Services/Metatile.cpp"
 #include  "Services/PackageStatusInformation.cpp"
 #include  "Services/PlotSpecification.cpp"
 #include  "Services/PrintLayout.cpp"

Modified: trunk/MgDev/Common/MapGuideCommon/MapLayer/Map.cpp
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/MapLayer/Map.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/MapLayer/Map.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -20,6 +20,12 @@
 #include "SAX2Parser.h"
 #include <map>
 
+/// rfc90 instead of a dependency on Common/Stylization/Bounds.h
+#define rs_max(a,b)     (((a) > (b)) ? (a) : (b))
+#define rs_min(a,b)     (((a) < (b)) ? (a) : (b))
+/// rfc90 instead of a dependency on Common/Stylization/StylizationDefs.h
+#define _METERS_PER_INCH 0.0254
+
 using namespace std;
 
 MG_IMPL_DYNCREATE(MgMap)
@@ -829,13 +835,13 @@
     //to FDO-ize this tile provider concept.
 
     MdfModel::TileStoreParameters* storeParams = tileset->GetTileStoreParameters();
-    if (storeParams->GetTileProvider() == MG_TILE_PROVIDER_DEFAULT) //NOXLATE
+    if (storeParams->GetTileProvider() == MG_TILE_PROVIDER_DEFAULT)
     {
         MdfModel::NameStringPairCollection* parameters = storeParams->GetParameters();
         for (INT32 i = 0; i < parameters->GetCount(); i++)
         {
             MdfModel::NameStringPair* nsp = parameters->GetAt(i);
-            if (nsp->GetName() == MG_TILE_PROVIDER_COMMON_PARAM_FINITESCALELIST) //NOXLATE
+            if (nsp->GetName() == MG_TILE_PROVIDER_COMMON_PARAM_FINITESCALELIST)
             {
                 Ptr<MgStringCollection> values = MgStringCollection::ParseCollection(nsp->GetValue(), L","); //NOXLATE
                 for (INT32 i = 0; i < values->GetCount(); i++)
@@ -1574,6 +1580,32 @@
     m_watermarkUsage = watermarkUsage;
 }
 
+/////////////////////////////////////////////////////////////////////
+// compute the tile coordinates from the x y index of the tiles and the meta tiling factor
+void MgMap::GetTileCoords(int metaTileFactor, int tileColumn, int tileRow,
+    int dpi, int tileWidth, int tileHeight,
+    double &tileMinX, double &tileMaxX, double &tileMinY, double & tileMaxY)
+{
+    Ptr<MgEnvelope> mapExtent = this->GetMapExtent();
+    Ptr<MgCoordinate> pt00 = mapExtent->GetLowerLeftCoordinate();
+    Ptr<MgCoordinate> pt11 = mapExtent->GetUpperRightCoordinate();
+    double mapMinX = rs_min(pt00->GetX(), pt11->GetX());
+    double mapMaxX = rs_max(pt00->GetX(), pt11->GetX());
+    double mapMinY = rs_min(pt00->GetY(), pt11->GetY());
+    double mapMaxY = rs_max(pt00->GetY(), pt11->GetY());
+
+    double scale = this->GetViewScale();
+    double metersPerUnit = this->GetMetersPerUnit();
+    double metersPerPixel = _METERS_PER_INCH / dpi;
+    double tileWidthMCS = (double)tileWidth  * metersPerPixel * scale / metersPerUnit;
+    double tileHeightMCS = (double)tileHeight * metersPerPixel * scale / metersPerUnit;
+
+    tileMinX = mapMinX + (double)(tileColumn)* tileWidthMCS;  // left edge
+    tileMaxX = mapMinX + (double)(tileColumn + metaTileFactor) * tileWidthMCS;  // right edge
+    tileMinY = mapMaxY - (double)(tileRow + metaTileFactor) * tileHeightMCS; // bottom edge
+    tileMaxY = mapMaxY - (double)(tileRow)* tileHeightMCS; // top edge
+}
+
 //////////////////////////////////////////////////////////////////
 // Returns the resource id of the Tile Set Definition that this group originates from.
 //

Modified: trunk/MgDev/Common/MapGuideCommon/MapLayer/Map.h
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/MapLayer/Map.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/MapLayer/Map.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -715,6 +715,17 @@
 
     virtual void Create(MgResourceService* resourceService, MgResourceIdentifier* mapDefinition, CREFSTRING mapName, bool strict);
 
+    /// \brief
+    /// compute the coordinates of a tile, increase size by meta tiling Factor sm_useMetaTiles
+    ///
+    /// \param in metaTileFactor, tileColumn, tileRow, dpi, tileWidth, tileHeight,
+    /// \param out tileMinX,tileMinY. tileMaxX , tileMaxY
+    /// min,max corners of the tile
+    ///
+    virtual void GetTileCoords(int metaTileFactor, int tileColumn, int tileRow,
+        int dpi, int tileWidth, int tileHeight,
+        double &tileMinX, double &tileMaxX, double &tileMinY, double & tileMaxY);
+
 protected:
 
     //////////////////////////////////////////////////////////////////

Modified: trunk/MgDev/Common/MapGuideCommon/Resources/mapguide_en.res
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/Resources/mapguide_en.res	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/Resources/mapguide_en.res	2019-05-27 12:24:20 UTC (rev 9510)
@@ -333,9 +333,6 @@
 MgStringEmpty                                         = The string cannot be empty.
 MgStringTooLong                                       = The string is too long.
 MgTagFieldNotFound                                    = The tag contained no fields.
-MgUnableToLockTileFile                                = Unable to lock the tile file.
-MgUnableToOpenLockFile                                = Unable to open the tile lock file.
-MgUnableToOpenTileFile                                = Unable to open the tile file.
 MgUnsupportedService                                  = The site/resource services cannot be enabled/disabled.
 MgUserAndGroupNotEmpty                                = Both the user and group are not empty.
 MgUserAndRoleNotEmpty                                 = Both the user and role are not empty.
@@ -431,6 +428,16 @@
 # T I L E  S E R V I C E
 # *****************************************************************************
 [TileService]
+MgFoundBlockingLockFile                                = Found a lock file for the current tile.
+MgGetTileWithOutMapSpecified                           = Calling GetTile without a valid mapdefinition or baselayer name.
+MgPermissionDeniedForResource                          = No permission to access the resource service for the current mapDefinition.
+MgNoMapSpecified                                       = Clearcache has been called with an empty map reference.
+MgTooLargeMetaTilingFactor                             = The specified metatiling Factor is too big, change serverconfig.ini.
+MgUnableToLockMetaTileFile                             = Unable to lock the current metatile file.
+MgUnableToLockTileFile                                 = Unable to lock the tile file.
+MgUnableToOpenLockFile                                 = Unable to open the tile lock file.
+MgUnableToOpenTileFile                                 = Unable to open the tile file.
+MgWrongSizeOfMetaTile                                  = Metatile has wrong size.
 MgTileProvider_Default_DisplayName                     = Default Tile Provider
 MgTileProvider_Default_Description                     = Default tile access provided by MapGuide. Supports MapGuide-managed tile path or user-defined path
 MgTileProvider_XYZ_DisplayName                         = XYZ Tile Provider

Modified: trunk/MgDev/Common/MapGuideCommon/Services/ImageFormats.cpp
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/Services/ImageFormats.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/Services/ImageFormats.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -30,3 +30,4 @@
 const STRING MgImageFormats::Png8   = L"PNG8";
 const STRING MgImageFormats::Tiff   = L"TIF";
 const STRING MgImageFormats::Raw    = L"RAW";
+const STRING MgImageFormats::Meta   = L"META";
\ No newline at end of file

Modified: trunk/MgDev/Common/MapGuideCommon/Services/ImageFormats.h
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/Services/ImageFormats.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/Services/ImageFormats.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -53,6 +53,10 @@
     /// \brief
     /// Raw format
     static const STRING Raw;    /// \if INTERNAL value("RAW") \endif
+
+    /// \brief
+    /// Meta format for rfc90 meta tiling
+    static const STRING Meta;   /// \if INTERNAL value("META") \endif
 };
 
 #endif

Copied: trunk/MgDev/Common/MapGuideCommon/Services/Metatile.cpp (from rev 9509, sandbox/jng/tiling_v2/Common/MapGuideCommon/Services/Metatile.cpp)
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/Services/Metatile.cpp	                        (rev 0)
+++ trunk/MgDev/Common/MapGuideCommon/Services/Metatile.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -0,0 +1,95 @@
+//
+//  Copyright (C) 2004-2019 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 "MapGuideCommon.h"
+#include "Metatile.h"
+
+MG_IMPL_DYNCREATE(MgMetatile)
+
+MgMetatile::MgMetatile() 
+    : m_width(0), m_height(0), m_metaTilingFactor(0), m_requestedWidth(0), m_requestedHeight(0)
+{
+
+}
+
+MgMetatile::MgMetatile(MgByteReader* image, INT32 width, INT32 height, INT32 requestedWidth, INT32 requestedHeight, CREFSTRING imageFormat, INT32 metaTilingFactor)
+    : m_width(width), m_height(height), m_requestedWidth(requestedWidth), m_requestedHeight(requestedHeight), m_imageFormat(imageFormat), m_metaTilingFactor(metaTilingFactor)
+{
+    m_image = SAFE_ADDREF(image);
+}
+
+MgMetatile::~MgMetatile()
+{
+    m_image = NULL;
+}
+
+MgByteReader* MgMetatile::GetImage()
+{
+    return SAFE_ADDREF(m_image.p);
+}
+
+INT32 MgMetatile::GetRequestedWidth()
+{
+    return m_requestedWidth;
+}
+
+INT32 MgMetatile::GetRequestedHeight()
+{
+    return m_requestedHeight;
+}
+
+INT32 MgMetatile::GetWidth()
+{
+    return m_width;
+}
+
+INT32 MgMetatile::GetHeight()
+{
+    return m_height;
+}
+
+STRING MgMetatile::GetTileImageFormat()
+{
+    return m_imageFormat;
+}
+
+INT32 MgMetatile::GetMetaTilingFactor()
+{
+    return m_metaTilingFactor;
+}
+
+void MgMetatile::Serialize(MgStream * stream)
+{
+    stream->WriteInt32(m_requestedWidth);
+    stream->WriteInt32(m_requestedHeight);
+    stream->WriteInt32(m_width);
+    stream->WriteInt32(m_height);
+    stream->WriteInt32(m_metaTilingFactor);
+    stream->WriteString(m_imageFormat);
+    stream->WriteObject(m_image);
+}
+
+void MgMetatile::Deserialize(MgStream * stream)
+{
+    stream->GetInt32(m_requestedWidth);
+    stream->GetInt32(m_requestedHeight);
+    stream->GetInt32(m_width);
+    stream->GetInt32(m_height);
+    stream->GetInt32(m_metaTilingFactor);
+    stream->GetString(m_imageFormat);
+    m_image = static_cast<MgByteReader*>(stream->GetObject());
+}

Copied: trunk/MgDev/Common/MapGuideCommon/Services/Metatile.h (from rev 9509, sandbox/jng/tiling_v2/Common/MapGuideCommon/Services/Metatile.h)
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/Services/Metatile.h	                        (rev 0)
+++ trunk/MgDev/Common/MapGuideCommon/Services/Metatile.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -0,0 +1,123 @@
+//
+//  Copyright (C) 2004-2019 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
+//
+
+#ifndef METATILE_H
+#define METATILE_H
+
+/// \cond INTERNAL
+/////////////////////////////////////////////////////////////////
+/// \brief
+/// Defines a raw image frame buffer from the result of a meta-tile
+/// rendering operation
+///
+/// \since 3.3
+class MG_MAPGUIDE_API MgMetatile : public MgSerializable
+{
+    MG_DECL_DYNCREATE()
+    DECLARE_CLASSNAME(MgMetatile)
+
+EXTERNAL_API:
+
+    /// \brief
+    /// Gets the meta-tile content. If no meta-tiling is performed, this is the tile image. Otherwise,
+    /// it is the raw image frame buffer (for sub-dividing into originally requested tile images)
+    MgByteReader* GetImage();
+
+    /// \brief
+    /// Gets the originally requested width of this tile
+    INT32 GetRequestedWidth();
+
+    /// \brief
+    /// Gets the originally requested height of this tile
+    INT32 GetRequestedHeight();
+
+    /// \brief
+    /// Gets the width of this metatile
+    INT32 GetWidth();
+
+    /// \brief
+    /// Gets the height of this metatile
+    
+    INT32 GetHeight();
+
+    /// \brief
+    /// Gets the meta-tiling factor.
+    INT32 GetMetaTilingFactor();
+
+    /// \brief
+    /// Gets the originally requested tile image format
+    STRING GetTileImageFormat();
+
+protected:
+
+    /// \brief
+    /// Dispose this object.
+    ///
+    /// \return
+    /// Returns nothing
+    ///
+    virtual void Dispose()
+    {
+        delete this;
+    }
+
+    /// \brief
+    /// Get the unique identifier for the class
+    ///
+    /// \return
+    /// Class Identifider.
+    ///
+    virtual INT32 GetClassId() { return m_cls_id; }
+
+CLASS_ID:
+    static const INT32 m_cls_id = MapGuide_RenderingService_Metatile;
+
+INTERNAL_API:
+    MgMetatile();
+    MgMetatile(MgByteReader* image, INT32 width, INT32 height, INT32 requestedWidth, INT32 requestedHeight, CREFSTRING imageFormat, INT32 metaTilingFactor);
+    virtual ~MgMetatile();
+
+    //////////////////////////////////////////////////////////////////
+    /// \brief
+    /// Serialize data to a stream
+    ///
+    /// \param stream
+    /// Stream
+    ///
+    virtual void Serialize(MgStream* stream);
+
+    //////////////////////////////////////////////////////////////////
+    /// \brief
+    /// Deserialize data from a stream
+    ///
+    /// \param stream
+    /// Stream
+    ///
+    virtual void Deserialize(MgStream* stream);
+
+private:
+    Ptr<MgByteReader> m_image;
+    INT32 m_width;
+    INT32 m_height;
+    INT32 m_requestedWidth;
+    INT32 m_requestedHeight;
+    INT32 m_metaTilingFactor;
+    STRING m_imageFormat;
+};
+/// \endcond
+
+#endif
\ No newline at end of file

Modified: trunk/MgDev/Common/MapGuideCommon/Services/ProxyRenderingService.cpp
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/Services/ProxyRenderingService.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/Services/ProxyRenderingService.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -139,6 +139,40 @@
     return (MgByteReader*)cmd.GetReturnValue().val.m_obj;
 }
 
+MgByteReader* MgProxyRenderingService::RenderTile(
+    MgMap* map,
+    CREFSTRING baseMapLayerGroupName,
+    INT32 tileColumn,
+    INT32 tileRow,
+    INT32 tileWidth,
+    INT32 tileHeight,
+    INT32 tileDpi,
+    CREFSTRING tileImageFormat,
+    double tileExtentOffset)
+{
+    MgCommand cmd;
+    cmd.ExecuteCommand(m_connProp,                                      // Connection
+        MgCommand::knObject,                            // Return type expected
+        MgRenderingServiceOpId::RenderTile2,            // Command Code
+        9,                                              // No of arguments
+        Rendering_Service,                              // Service Id
+        BUILD_VERSION(3, 3, 0),                         // Operation version
+        MgCommand::knObject, map,                       // Argument#1
+        MgCommand::knString, &baseMapLayerGroupName,    // Argument#2
+        MgCommand::knInt32, tileColumn,                 // Argument#3
+        MgCommand::knInt32, tileRow,                    // Argument#4
+        MgCommand::knInt32, tileWidth,                  // Argument#5
+        MgCommand::knInt32, tileHeight,                 // Argument#6
+        MgCommand::knInt32, tileDpi,                    // Argument#7
+        MgCommand::knString, &tileImageFormat,          // Argument#8
+        MgCommand::knDouble, tileExtentOffset,          // Argument#9
+        MgCommand::knNone);                             // End of arguments
+
+    SetWarning(cmd.GetWarningObject());
+
+    return (MgByteReader*)cmd.GetReturnValue().val.m_obj;
+}
+
 MgByteReader* MgProxyRenderingService::RenderTileXYZ(MgMap* map,
                                                      CREFSTRING baseMapLayerGroupName,
                                                      INT32 x,
@@ -193,6 +227,37 @@
     return (MgByteReader*)cmd.GetReturnValue().val.m_obj;
 }
 
+MgByteReader* MgProxyRenderingService::RenderTileXYZ(MgMap* map,
+    CREFSTRING baseMapLayerGroupName,
+    INT32 x,
+    INT32 y,
+    INT32 z,
+    INT32 dpi,
+    CREFSTRING tileImageFormat,
+    double tileExtentOffset)
+{
+    MgCommand cmd;
+    cmd.ExecuteCommand(m_connProp,                                      // Connection
+        MgCommand::knObject,                            // Return type expected
+        MgRenderingServiceOpId::RenderTileXYZ2,         // Command Code
+        8,                                              // No of arguments
+        Rendering_Service,                              // Service Id
+        BUILD_VERSION(3, 3, 0),                         // Operation version
+        MgCommand::knObject, map,                       // Argument#1
+        MgCommand::knString, &baseMapLayerGroupName,    // Argument#2
+        MgCommand::knInt32, x,                          // Argument#3
+        MgCommand::knInt32, y,                          // Argument#4
+        MgCommand::knInt32, z,                          // Argument#5
+        MgCommand::knInt32, dpi,                        // Argument#6
+        MgCommand::knString, &tileImageFormat,          // Argument#7
+        MgCommand::knDouble, tileExtentOffset,          // Argument#8
+        MgCommand::knNone);                             // End of arguments
+
+    SetWarning(cmd.GetWarningObject());
+
+    return (MgByteReader*)cmd.GetReturnValue().val.m_obj;
+}
+
 MgByteReader * MgProxyRenderingService::RenderTileUTFGrid(MgMap * map, CREFSTRING baseMapLayerGroupName, INT32 x, INT32 y, INT32 z, INT32 dpi)
 {
     MgCommand cmd;
@@ -1267,4 +1332,100 @@
     SetWarning(cmd.GetWarningObject());
 
     return (MgByteReader*)cmd.GetReturnValue().val.m_obj;
+}
+
+MgMetatile* MgProxyRenderingService::RenderMetatile(
+    MgMap* map,
+    CREFSTRING baseMapLayerGroupName,
+    INT32 tileColumn,
+    INT32 tileRow,
+    INT32 tileWidth,
+    INT32 tileHeight,
+    INT32 tileDpi,
+    CREFSTRING tileImageFormat,
+    double tileExtentOffset,
+    INT32 metaTilingFactor)
+{
+    MgCommand cmd;
+    cmd.ExecuteCommand(m_connProp,                      // Connection
+        MgCommand::knObject,                            // Return type expected
+        MgRenderingServiceOpId::RenderMetatile,         // Command Code
+        10,                                             // No of arguments
+        Rendering_Service,                              // Service Id
+        BUILD_VERSION(3, 3, 0),                         // Operation version
+        MgCommand::knObject, map,                       // Argument#1
+        MgCommand::knString, &baseMapLayerGroupName,    // Argument#2
+        MgCommand::knInt32, tileColumn,                 // Argument#3
+        MgCommand::knInt32, tileRow,                    // Argument#4
+        MgCommand::knInt32, tileWidth,                  // Argument#5
+        MgCommand::knInt32, tileHeight,                 // Argument#6
+        MgCommand::knInt32, tileDpi,                    // Argument#7
+        MgCommand::knString, &tileImageFormat,          // Argument#8
+        MgCommand::knDouble, tileExtentOffset,          // Argument#9
+        MgCommand::knInt32, metaTilingFactor,           // Argument#10
+        MgCommand::knNone);                             // End of arguments
+
+    SetWarning(cmd.GetWarningObject());
+
+    return (MgMetatile*)cmd.GetReturnValue().val.m_obj;
+}
+
+MgMetatile* MgProxyRenderingService::RenderMetatileXYZ(
+    MgMap* map,
+    CREFSTRING baseMapLayerGroupName,
+    INT32 x,
+    INT32 y,
+    INT32 z,
+    INT32 dpi,
+    CREFSTRING tileImageFormat,
+    double tileExtentOffset,
+    INT32 metaTilingFactor)
+{
+    MgCommand cmd;
+    cmd.ExecuteCommand(m_connProp,                      // Connection
+        MgCommand::knObject,                            // Return type expected
+        MgRenderingServiceOpId::RenderTileXYZ2,         // Command Code
+        9,                                              // No of arguments
+        Rendering_Service,                              // Service Id
+        BUILD_VERSION(3, 3, 0),                         // Operation version
+        MgCommand::knObject, map,                       // Argument#1
+        MgCommand::knString, &baseMapLayerGroupName,    // Argument#2
+        MgCommand::knInt32, x,                          // Argument#3
+        MgCommand::knInt32, y,                          // Argument#4
+        MgCommand::knInt32, z,                          // Argument#5
+        MgCommand::knInt32, dpi,                        // Argument#6
+        MgCommand::knString, &tileImageFormat,          // Argument#7
+        MgCommand::knDouble, tileExtentOffset,          // Argument#8
+        MgCommand::knInt32, metaTilingFactor,           // Argument#9
+        MgCommand::knNone);                             // End of arguments
+
+    SetWarning(cmd.GetWarningObject());
+
+    return (MgMetatile*)cmd.GetReturnValue().val.m_obj;
+}
+
+MgByteReader* MgProxyRenderingService::RenderTileFromMetaTile(
+    MgMap* map,
+    MgMetatile* metaTile,
+    CREFSTRING rendererName,
+    INT32 subTileX,
+    INT32 subTileY)
+{
+    MgCommand cmd;
+    cmd.ExecuteCommand(m_connProp,                      // Connection
+        MgCommand::knObject,                            // Return type expected
+        MgRenderingServiceOpId::RenderTileFromMetaTile, // Command Code
+        9,                                              // No of arguments
+        Rendering_Service,                              // Service Id
+        BUILD_VERSION(3, 3, 0),                         // Operation version
+        MgCommand::knObject, map,                       // Argument#1
+        MgCommand::knObject, metaTile,                  // Argument#2
+        MgCommand::knString, &rendererName,             // Argument#3
+        MgCommand::knInt32, subTileX,                   // Argument#4
+        MgCommand::knInt32, subTileY,                   // Argument#5
+        MgCommand::knNone);                             // End of arguments
+
+    SetWarning(cmd.GetWarningObject());
+
+    return (MgByteReader*)cmd.GetReturnValue().val.m_obj;
 }
\ No newline at end of file

Modified: trunk/MgDev/Common/MapGuideCommon/Services/ProxyRenderingService.h
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/Services/ProxyRenderingService.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/Services/ProxyRenderingService.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -107,6 +107,60 @@
 
     /////////////////////////////////////////////////////////////////
     /// \brief
+    /// Returns the specified base map tile for the given map.
+    ///
+    /// \remarks
+    /// This method only renders the given tile. No tile caching is performed
+    /// by this method. To render and cache the tile, use the 
+    /// \link MgTileService::GetTile GetTile \endlink method instead. However,
+    /// using that method will use default tile width/height/dpi/format specified
+    /// in your MapGuide Server configuration
+    ///
+    /// \param map
+    /// Input
+    /// map object containing current state of map.
+    /// \param baseMapLayerGroupName
+    /// Input
+    /// Specifies the name of the baseMapLayerGroup for which to render the tile.
+    /// \param tileColumn
+    /// Input
+    /// Specifies the column index of the tile to return.
+    /// \param tileRow
+    /// Input
+    /// Specifies the row index of the tile to return.
+    /// \param tileWidth
+    /// Input
+    /// Specifies the width of the tile to return.
+    /// \param tileHeight
+    /// Input
+    /// Specifies the height of the tile to return.
+    /// \param tileDpi
+    /// Input
+    /// Specifies the dpi the tile to return.
+    /// \param tileImageFormat
+    /// Input
+    /// Specifies the image format of the tile. See \link MgImageFormats \endlink
+    /// \param tileExtentOffset
+    /// Specifies the ratio by which the tile to be rendered should be "buffered out". The tile will be rendered at the specified width
+    /// multiplied by the given ration, which will be cropped back to the original requested size after rendering. This is to improve
+    /// label placement on rendered tiles by giving extra "breathing space" to label placement algorithms.
+    ///
+    /// \return
+    /// A byte reader containing the rendered tile image.
+    ///
+    virtual MgByteReader* RenderTile(
+        MgMap* map,
+        CREFSTRING baseMapLayerGroupName,
+        INT32 tileColumn,
+        INT32 tileRow,
+        INT32 tileWidth,
+        INT32 tileHeight,
+        INT32 tileDpi,
+        CREFSTRING tileImageFormat,
+        double tileExtentOffset);
+
+    /////////////////////////////////////////////////////////////////
+    /// \brief
     /// Returns the specified map tile for the given map. Tile structure is
     /// based on the XYZ tiling scheme used by Google Maps, OpenStreetMap, and
     /// others
@@ -179,6 +233,51 @@
 
     /////////////////////////////////////////////////////////////////
     /// \brief
+    /// Returns the specified map tile for the given map. Tile structure is
+    /// based on the XYZ tiling scheme used by Google Maps, OpenStreetMap, and
+    /// others
+    ///
+    /// \param map
+    /// Input
+    /// map object containing current state of map.
+    /// \param baseMapLayerGroupName
+    /// Input
+    /// Specifies the name of the baseMapLayerGroup for which to render the tile.
+    /// \param x
+    /// Input
+    /// Specifies the row index of the tile to return.
+    /// \param y
+    /// Input
+    /// Specifies the column index of the tile to return.
+    /// \param z
+    /// Input
+    /// Specifies the zoom level of the tile to return.
+    /// \param dpi
+    /// Input
+    /// Specifies the dpi of the tile to return.
+    /// \param tileImageFormat
+    /// Input
+    /// Specifies the image format of the tile to return.
+    /// \param tileExtentOffset
+    /// Specifies the ratio by which the tile to be rendered should be "buffered out". The tile will be rendered at the specified width
+    /// multiplied by the given ration, which will be cropped back to the original requested size after rendering. This is to improve
+    /// label placement on rendered tiles by giving extra "breathing space" to label placement algorithms.
+    ///
+    /// \return
+    /// A byte reader containing the rendered tile image.
+    ///
+    virtual MgByteReader* RenderTileXYZ(
+        MgMap* map,
+        CREFSTRING baseMapLayerGroupName,
+        INT32 x,
+        INT32 y,
+        INT32 z,
+        INT32 dpi,
+        CREFSTRING tileImageFormat,
+        double tileExtentOffset);
+
+    /////////////////////////////////////////////////////////////////
+    /// \brief
     /// Returns the specified UTFGrid tile for the given map. Tile structure is
     /// based on the XYZ tiling scheme used by Google Maps, OpenStreetMap, and
     /// others
@@ -1005,6 +1104,149 @@
         bool bIncludeFeatureBBOX,
         bool bIncludeGeometry);
 
+    /////////////////////////////////////////////////////////////////
+    /// \brief
+    /// Returns the specified base map tile for the given map.
+    ///
+    /// \remarks
+    /// This method only renders the given tile. No tile caching is performed
+    /// by this method. To render and cache the tile, use the 
+    /// \link MgTileService::GetTile GetTile \endlink method instead. However,
+    /// using that method will use default tile width/height/dpi/format specified
+    /// in your MapGuide Server configuration
+    ///
+    /// \param map
+    /// Input
+    /// map object containing current state of map.
+    /// \param baseMapLayerGroupName
+    /// Input
+    /// Specifies the name of the baseMapLayerGroup for which to render the tile.
+    /// \param tileColumn
+    /// Input
+    /// Specifies the column index of the tile to return.
+    /// \param tileRow
+    /// Input
+    /// Specifies the row index of the tile to return.
+    /// \param tileWidth
+    /// Input
+    /// Specifies the width of the tile to return.
+    /// \param tileHeight
+    /// Input
+    /// Specifies the height of the tile to return.
+    /// \param tileDpi
+    /// Input
+    /// Specifies the dpi the tile to return.
+    /// \param tileImageFormat
+    /// Input
+    /// Specifies the image format of the tile. See \link MgImageFormats \endlink
+    /// \param tileExtentOffset
+    /// Input
+    /// Specifies the ratio by which the tile to be rendered should be "buffered out". The tile will be rendered at the specified width
+    /// multiplied by the given ratio, which will be cropped back to the original requested size after rendering. This is to improve
+    /// label placement on rendered tiles by giving extra "breathing space" to label placement algorithms.
+    /// \param metaTilingFactor
+    /// Input
+    /// The meta-tiling factor. If less than or equal to 1, no meta-tiling is done and the returned meta-tile can be extracted
+    /// as the orignally requested tile image. If greater than 1, a tile that is m times bigger than the requested tile is rendered
+    /// (where m is the specified tiling factor) and the raw image frame buffer of this meta-tile is returned instead.
+    ///
+    /// \return
+    /// A meta-tile with sufficient information for the consumer to properly sub-divide this back into sub-tiles of the
+    /// originally requested size.
+    ///
+    /// \since 3.3
+    virtual MgMetatile* RenderMetatile(
+        MgMap* map,
+        CREFSTRING baseMapLayerGroupName,
+        INT32 tileColumn,
+        INT32 tileRow,
+        INT32 tileWidth,
+        INT32 tileHeight,
+        INT32 tileDpi,
+        CREFSTRING tileImageFormat,
+        double tileExtentOffset,
+        INT32 metaTilingFactor);
+
+    /////////////////////////////////////////////////////////////////
+    /// \brief
+    /// Returns the specified map tile for the given map. Tile structure is
+    /// based on the XYZ tiling scheme used by Google Maps, OpenStreetMap, and
+    /// others
+    ///
+    /// \param map
+    /// Input
+    /// map object containing current state of map.
+    /// \param baseMapLayerGroupName
+    /// Input
+    /// Specifies the name of the baseMapLayerGroup for which to render the tile.
+    /// \param x
+    /// Input
+    /// Specifies the row index of the tile to return.
+    /// \param y
+    /// Input
+    /// Specifies the column index of the tile to return.
+    /// \param z
+    /// Input
+    /// Specifies the zoom level of the tile to return.
+    /// \param dpi
+    /// Input
+    /// Specifies the dpi of the tile to return.
+    /// \param tileImageFormat
+    /// Input
+    /// Specifies the image format of the tile to return.
+    /// \param tileExtentOffset
+    /// Input
+    /// Specifies the ratio by which the tile to be rendered should be "buffered out". The tile will be rendered at the specified width
+    /// multiplied by the given ratio, which will be cropped back to the original requested size after rendering. This is to improve
+    /// label placement on rendered tiles by giving extra "breathing space" to label placement algorithms.
+    /// \param metaTilingFactor
+    /// Input
+    /// The meta-tiling factor. If less than or equal to 1, no meta-tiling is done and the returned meta-tile can be extracted
+    /// as the orignally requested tile image. If greater than 1, a tile that is m times bigger than the requested tile is rendered
+    /// (where m is the specified tiling factor) and the raw image frame buffer of this meta-tile is returned instead.
+    ///
+    /// \return
+    /// A meta-tile with sufficient information for the consumer to properly sub-divide this back into sub-tiles of the
+    /// originally requested size.
+    ///
+    /// \since 3.3
+    virtual MgMetatile* RenderMetatileXYZ(
+        MgMap* map,
+        CREFSTRING baseMapLayerGroupName,
+        INT32 x,
+        INT32 y,
+        INT32 z,
+        INT32 dpi,
+        CREFSTRING tileImageFormat,
+        double tileExtentOffset,
+        INT32 metaTilingFactor);
+
+    /////////////////////////////////////////////////////////////////
+    /// \brief
+    /// Returns the requested sub-tile from the given metatile
+    ///
+    /// \param map
+    /// Input
+    /// map object containing current state of map.
+    /// \param metaTile
+    /// Input
+    /// The meta-tile from which a subtile is being requested for
+    /// \param rendererName
+    /// Input
+    /// The name of the renderer to create for sub-tile slicing
+    /// \param subTileX
+    /// Input
+    /// The x sub-tile coordinate of the meta-tile to request
+    /// \param subTileY
+    /// Input
+    /// The y sub-tile coordinate of the meta-tile to request
+    ///
+    /// \return
+    /// The requested sub-tile
+    ///
+    /// \since 3.3
+    virtual MgByteReader* RenderTileFromMetaTile(MgMap* map, MgMetatile* metaTile, CREFSTRING rendererName, INT32 subTileX, INT32 subTileY);
+
 protected:
 
     //////////////////////////////////////////////////////////////////

Modified: trunk/MgDev/Common/MapGuideCommon/Services/RenderingDefs.h
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/Services/RenderingDefs.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/Services/RenderingDefs.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -47,6 +47,8 @@
     static const int RenderMap7                 = 0x1111E913;
     static const int RenderTileUTFGrid          = 0x1111E914;
     static const int QueryFeatureProperties3    = 0x1111E915;
+    static const int RenderMetatile             = 0x1111E916;
+    static const int RenderTileFromMetaTile     = 0x1111E917;
 };
 /// \endcond
 

Modified: trunk/MgDev/Common/MapGuideCommon/Services/RenderingService.h
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/Services/RenderingService.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/Services/RenderingService.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -121,6 +121,61 @@
 
     /////////////////////////////////////////////////////////////////
     /// \brief
+    /// Returns the specified base map tile for the given map.
+    ///
+    /// \remarks
+    /// This method only renders the given tile. No tile caching is performed
+    /// by this method. To render and cache the tile, use the 
+    /// \link MgTileService::GetTile GetTile \endlink method instead. However,
+    /// using that method will use default tile width/height/dpi/format specified
+    /// in your MapGuide Server configuration
+    ///
+    /// \param map
+    /// Input
+    /// map object containing current state of map.
+    /// \param baseMapLayerGroupName
+    /// Input
+    /// Specifies the name of the baseMapLayerGroup for which to render the tile.
+    /// \param tileColumn
+    /// Input
+    /// Specifies the column index of the tile to return.
+    /// \param tileRow
+    /// Input
+    /// Specifies the row index of the tile to return.
+    /// \param tileWidth
+    /// Input
+    /// Specifies the width of the tile to return.
+    /// \param tileHeight
+    /// Input
+    /// Specifies the height of the tile to return.
+    /// \param tileDpi
+    /// Input
+    /// Specifies the dpi the tile to return.
+    /// \param tileImageFormat
+    /// Input
+    /// Specifies the image format of the tile. See \link MgImageFormats \endlink
+    /// \param tileExtentOffset
+    /// Specifies the ratio by which the tile to be rendered should be "buffered out". The tile will be rendered at the specified width
+    /// multiplied by the given ration, which will be cropped back to the original requested size after rendering. This is to improve
+    /// label placement on rendered tiles by giving extra "breathing space" to label placement algorithms.
+    ///
+    /// \return
+    /// A byte reader containing the rendered tile image.
+    ///
+    /// \since 3.3
+    virtual MgByteReader* RenderTile(
+        MgMap* map,
+        CREFSTRING baseMapLayerGroupName,
+        INT32 tileColumn,
+        INT32 tileRow,
+        INT32 tileWidth,
+        INT32 tileHeight,
+        INT32 tileDpi,
+        CREFSTRING tileImageFormat,
+        double tileExtentOffset) = 0;
+
+    /////////////////////////////////////////////////////////////////
+    /// \brief
     /// Returns the specified map tile for the given map. Tile structure is
     /// based on the XYZ tiling scheme used by Google Maps, OpenStreetMap, and
     /// others
@@ -194,6 +249,51 @@
 
     /////////////////////////////////////////////////////////////////
     /// \brief
+    /// Returns the specified map tile for the given map. Tile structure is
+    /// based on the XYZ tiling scheme used by Google Maps, OpenStreetMap, and
+    /// others
+    ///
+    /// \param map
+    /// Input
+    /// map object containing current state of map.
+    /// \param baseMapLayerGroupName
+    /// Input
+    /// Specifies the name of the baseMapLayerGroup for which to render the tile.
+    /// \param x
+    /// Input
+    /// Specifies the row index of the tile to return.
+    /// \param y
+    /// Input
+    /// Specifies the column index of the tile to return.
+    /// \param z
+    /// Input
+    /// Specifies the zoom level of the tile to return.
+    /// \param dpi
+    /// Input
+    /// Specifies the dpi of the tile to return.
+    /// \param tileImageFormat
+    /// Input
+    /// Specifies the image format of the tile to return.
+    /// \param tileExtentOffset
+    /// Specifies the ratio by which the tile to be rendered should be "buffered out". The tile will be rendered at the specified width
+    /// multiplied by the given ration, which will be cropped back to the original requested size after rendering. This is to improve
+    /// label placement on rendered tiles by giving extra "breathing space" to label placement algorithms.
+    ///
+    /// \return
+    /// A byte reader containing the rendered tile image.
+    ///
+    virtual MgByteReader* RenderTileXYZ(
+        MgMap* map,
+        CREFSTRING baseMapLayerGroupName,
+        INT32 x,
+        INT32 y,
+        INT32 z,
+        INT32 dpi,
+        CREFSTRING tileImageFormat,
+        double tileExtentOffset) = 0;
+
+    /////////////////////////////////////////////////////////////////
+    /// \brief
     /// Returns the specified UTFGrid tile for the given map. Tile structure is
     /// based on the XYZ tiling scheme used by Google Maps, OpenStreetMap, and
     /// others
@@ -1017,6 +1117,152 @@
         bool bIncludeFeatureBBOX,
         bool bIncludeGeometry) = 0;
 
+EXTERNAL_API:
+
+    /////////////////////////////////////////////////////////////////
+    /// \brief
+    /// Returns the specified base map tile for the given map.
+    ///
+    /// \remarks
+    /// This method only renders the given tile. No tile caching is performed
+    /// by this method. To render and cache the tile, use the 
+    /// \link MgTileService::GetTile GetTile \endlink method instead. However,
+    /// using that method will use default tile width/height/dpi/format specified
+    /// in your MapGuide Server configuration
+    ///
+    /// \param map
+    /// Input
+    /// map object containing current state of map.
+    /// \param baseMapLayerGroupName
+    /// Input
+    /// Specifies the name of the baseMapLayerGroup for which to render the tile.
+    /// \param tileColumn
+    /// Input
+    /// Specifies the column index of the tile to return.
+    /// \param tileRow
+    /// Input
+    /// Specifies the row index of the tile to return.
+    /// \param tileWidth
+    /// Input
+    /// Specifies the width of the tile to return.
+    /// \param tileHeight
+    /// Input
+    /// Specifies the height of the tile to return.
+    /// \param tileDpi
+    /// Input
+    /// Specifies the dpi the tile to return.
+    /// \param tileImageFormat
+    /// Input
+    /// Specifies the image format of the tile. See \link MgImageFormats \endlink
+    /// \param tileExtentOffset
+    /// Input
+    /// Specifies the ratio by which the tile to be rendered should be "buffered out". The tile will be rendered at the specified width
+    /// multiplied by the given ratio, which will be cropped back to the original requested size after rendering. This is to improve
+    /// label placement on rendered tiles by giving extra "breathing space" to label placement algorithms.
+    /// \param metaTilingFactor
+    /// Input
+    /// The meta-tiling factor. If less than or equal to 1, no meta-tiling is done and the returned meta-tile can be extracted
+    /// as the orignally requested tile image. If greater than 1, a tile that is m times bigger than the requested tile is rendered
+    /// (where m is the specified tiling factor) and the raw image frame buffer of this meta-tile is returned instead.
+    ///
+    /// \return
+    /// A meta-tile with sufficient information for the consumer to properly sub-divide this back into sub-tiles of the
+    /// originally requested size.
+    ///
+    /// \since 3.3
+    virtual MgMetatile* RenderMetatile(
+        MgMap* map,
+        CREFSTRING baseMapLayerGroupName,
+        INT32 tileColumn,
+        INT32 tileRow,
+        INT32 tileWidth,
+        INT32 tileHeight,
+        INT32 tileDpi,
+        CREFSTRING tileImageFormat,
+        double tileExtentOffset,
+        INT32 metaTilingFactor) = 0;
+
+    /////////////////////////////////////////////////////////////////
+    /// \brief
+    /// Returns the specified map tile for the given map. Tile structure is
+    /// based on the XYZ tiling scheme used by Google Maps, OpenStreetMap, and
+    /// others
+    ///
+    /// \param map
+    /// Input
+    /// map object containing current state of map.
+    /// \param baseMapLayerGroupName
+    /// Input
+    /// Specifies the name of the baseMapLayerGroup for which to render the tile.
+    /// \param x
+    /// Input
+    /// Specifies the row index of the tile to return.
+    /// \param y
+    /// Input
+    /// Specifies the column index of the tile to return.
+    /// \param z
+    /// Input
+    /// Specifies the zoom level of the tile to return.
+    /// \param dpi
+    /// Input
+    /// Specifies the dpi of the tile to return.
+    /// \param tileImageFormat
+    /// Input
+    /// Specifies the image format of the tile to return.
+    /// \param tileExtentOffset
+    /// Input
+    /// Specifies the ratio by which the tile to be rendered should be "buffered out". The tile will be rendered at the specified width
+    /// multiplied by the given ratio, which will be cropped back to the original requested size after rendering. This is to improve
+    /// label placement on rendered tiles by giving extra "breathing space" to label placement algorithms.
+    /// \param metaTilingFactor
+    /// Input
+    /// The meta-tiling factor. If less than or equal to 1, no meta-tiling is done and the returned meta-tile can be extracted
+    /// as the orignally requested tile image. If greater than 1, a tile that is m times bigger than the requested tile is rendered
+    /// (where m is the specified tiling factor) and the raw image frame buffer of this meta-tile is returned instead.
+    ///
+    /// \return
+    /// A meta-tile with sufficient information for the consumer to properly sub-divide this back into sub-tiles of the
+    /// originally requested size.
+    ///
+    /// \since 3.3
+    virtual MgMetatile* RenderMetatileXYZ(
+        MgMap* map,
+        CREFSTRING baseMapLayerGroupName,
+        INT32 x,
+        INT32 y,
+        INT32 z,
+        INT32 dpi,
+        CREFSTRING tileImageFormat,
+        double tileExtentOffset,
+        INT32 metaTilingFactor) = 0;
+
+
+    /////////////////////////////////////////////////////////////////
+    /// \brief
+    /// Returns the requested sub-tile from the given metatile
+    ///
+    /// \param map
+    /// Input
+    /// map object containing current state of map.
+    /// \param metaTile
+    /// Input
+    /// The meta-tile from which a subtile is being requested for
+    /// \param rendererName
+    /// Input
+    /// The name of the renderer to create for sub-tile slicing
+    /// \param subTileX
+    /// Input
+    /// The x sub-tile coordinate of the meta-tile to request
+    /// \param subTileY
+    /// Input
+    /// The y sub-tile coordinate of the meta-tile to request
+    ///
+    /// \return
+    /// The requested sub-tile
+    ///
+    /// \since 3.3
+    virtual MgByteReader* RenderTileFromMetaTile(MgMap* map, MgMetatile* metaTile, CREFSTRING rendererName, INT32 subTileX, INT32 subTileY) = 0;
+
 protected:
 
     /////////////////////////////////////////////////////////////////

Modified: trunk/MgDev/Common/MapGuideCommon/Services/TileDefs.h
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/Services/TileDefs.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/Services/TileDefs.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -59,12 +59,17 @@
 
 #define MG_TILE_PROVIDER_DEFAULT L"Default"
 #define MG_TILE_PROVIDER_XYZ L"XYZ"
-#define MG_TILE_PROVIDER_COMMON_PARAM_TILEPATH         L"TilePath"
-#define MG_TILE_PROVIDER_COMMON_PARAM_TILEWIDTH        L"TileWidth"
-#define MG_TILE_PROVIDER_COMMON_PARAM_TILEHEIGHT       L"TileHeight"
-#define MG_TILE_PROVIDER_COMMON_PARAM_TILEFORMAT       L"TileFormat"
-#define MG_TILE_PROVIDER_COMMON_PARAM_RENDERONLY       L"RenderOnly"
-#define MG_TILE_PROVIDER_COMMON_PARAM_COORDINATESYSTEM L"CoordinateSystem"
-#define MG_TILE_PROVIDER_COMMON_PARAM_FINITESCALELIST  L"FiniteScaleList"
+#define MG_TILE_PROVIDER_COMMON_PARAM_TILEPATH           L"TilePath"
+#define MG_TILE_PROVIDER_COMMON_PARAM_TILEWIDTH          L"TileWidth"
+#define MG_TILE_PROVIDER_COMMON_PARAM_TILEHEIGHT         L"TileHeight"
+#define MG_TILE_PROVIDER_COMMON_PARAM_TILEFORMAT         L"TileFormat"
+#define MG_TILE_PROVIDER_COMMON_PARAM_RENDERONLY         L"RenderOnly"
+#define MG_TILE_PROVIDER_COMMON_PARAM_COORDINATESYSTEM   L"CoordinateSystem"
+#define MG_TILE_PROVIDER_COMMON_PARAM_FINITESCALELIST    L"FiniteScaleList"
+#define MG_TILE_PROVIDER_COMMON_PARAM_TILEEXTENTOFFSET   L"TileExtentOffset"
+#define MG_TILE_PROVIDER_COMMON_PARAM_METATILEFACTOR     L"MetaTileFactor"
+#define MG_TILE_PROVIDER_COMMON_PARAM_METATILELOCKMETHOD L"MetaTileLockMethod"
 
+#define MG_METATILE_MAX_FACTOR 8
+
 #endif

Modified: trunk/MgDev/Common/MapGuideCommon/System/MapGuideCommonClassId.h
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/System/MapGuideCommonClassId.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/System/MapGuideCommonClassId.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -130,6 +130,7 @@
 #define MapGuide_RenderingService_RenderingService                  MAPGUIDE_RENDERINGSERVICE_ID+0
 #define MapGuide_RenderingService_FeatureInformation                MAPGUIDE_RENDERINGSERVICE_ID+1
 #define MapGuide_RenderingService_RenderingOptions                  MAPGUIDE_RENDERINGSERVICE_ID+2
+#define MapGuide_RenderingService_Metatile                          MAPGUIDE_RENDERINGSERVICE_ID+3
 
 // Resource Service
 #define MapGuide_ResourceService_ResourceService                    MAPGUIDE_RESOURCESERVICE_ID+0

Modified: trunk/MgDev/Common/MapGuideCommon/System/MapGuideCommonFactory.cpp
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/System/MapGuideCommonFactory.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/MapGuideCommon/System/MapGuideCommonFactory.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -111,6 +111,7 @@
     fact->Register(MapGuide_MappingService_MapPlotCollection, MgMapPlotCollection::CreateObject);
     fact->Register(MapGuide_RenderingService_FeatureInformation, MgFeatureInformation::CreateObject);
     fact->Register(MapGuide_RenderingService_RenderingOptions, MgRenderingOptions::CreateObject);
+    fact->Register(MapGuide_RenderingService_Metatile, MgMetatile::CreateObject);
     fact->Register(PlatformBase_FeatureService_FeatureTransaction, MgProxyFeatureTransaction::CreateObject);
     fact->Register(MapGuide_Service_SqlResult, MgSqlResult::CreateObject);
 

Modified: trunk/MgDev/Common/Renderers/AGGRenderer.cpp
===================================================================
--- trunk/MgDev/Common/Renderers/AGGRenderer.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/Renderers/AGGRenderer.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -65,6 +65,8 @@
 #include "SE_SymbolDefProxies.h"
 
 #include "RenderUtil.h"
+// simple define to avoid dependency cycles
+#define MgImageFormats_Meta L"META"
 
 using namespace DWFToolkit;
 using namespace DWFCore;
@@ -257,12 +259,30 @@
 
 //////////////////////////////////////////////////////////////////////////////
 // Return the rendered image passed in via the imagebuffer (m_rows) as
-// a bytestream in the given image format using the provided colorPalette
-// if given.
+// a bytestream in the given image format using the optional provided colorPalette.
+// In MetaTileing mode return the imagebuffer copied in a new  RS_ByteData object.
 RS_ByteData* AGGRenderer::Save(const RS_String& format, int width, int height,
-                               RS_ColorVector* baseColorPalette)
+                               RS_ColorVector* baseColorPalette, unsigned int* imagebuffer)
 {
-    return AGGImageIO::Save(format, m_rows, m_width, m_height, width, height, m_bgcolor, baseColorPalette);
+    if (format == MgImageFormats_Meta)
+    {
+        //        if (imagebuffer == NULL)  // this musnt happen
+        //            return NULL;
+
+        // TODO: For extent-based tiles like XYZ, it means our framebuffer represents a non-square
+        // tile, which technically means we're wasting allocated memory on pixels that aren't being 
+        // stored for such cases, but this being the first cut implementation, we're being extremely
+        // conservative here. If there is indeed wasted space. So be it.
+        int imagebufferByteSize = width * height * 4; // multiply 32 bit pixel by 4 bytes
+        return new RS_ByteData((unsigned char*)m_rows, imagebufferByteSize);
+    }
+    else 
+    {
+        if (imagebuffer)       // use imagebuffer from previous meta tiling pass
+            return AGGImageIO::Save(format, imagebuffer, m_width, m_height, width, height, m_bgcolor, baseColorPalette);
+        else
+            return AGGImageIO::Save(format, m_rows, m_width, m_height, width, height, m_bgcolor, baseColorPalette);
+    }
 }
 
 

Modified: trunk/MgDev/Common/Renderers/AGGRenderer.h
===================================================================
--- trunk/MgDev/Common/Renderers/AGGRenderer.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Common/Renderers/AGGRenderer.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -152,7 +152,7 @@
     RENDERERS_API void Save(const RS_String& filename, const RS_String& format);
     RENDERERS_API void Save(const RS_String& filename, const RS_String& format, int width, int height);
     RENDERERS_API RS_ByteData* Save(const RS_String& format, int width, int height,
-                                    RS_ColorVector* baseColorPalette = NULL);
+                                    RS_ColorVector* baseColorPalette = NULL, unsigned int* imagebuffer = NULL);
 
     RENDERERS_API void Combine(const RS_String& fileIn1, const RS_String& fileIn2, const RS_String& fileOut);
     RENDERERS_API void SetWorldToScreenTransform(SE_Matrix& xform);

Modified: trunk/MgDev/Server/src/PostBuild/PostBuild.mak
===================================================================
--- trunk/MgDev/Server/src/PostBuild/PostBuild.mak	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/PostBuild/PostBuild.mak	2019-05-27 12:24:20 UTC (rev 9510)
@@ -166,6 +166,9 @@
         ..\..\bin\UnitTestFiles\UT_BaseMap.mdf \
         ..\..\bin\UnitTestFiles\UT_BaseMap.tsd \
         ..\..\bin\UnitTestFiles\UT_XYZ.tsd \
+        ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd \
+        ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd \
+        ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd \
         ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf \
         ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf \
         ..\..\bin\UnitTestFiles\UT_Parcels.fs \
@@ -443,6 +446,9 @@
         ..\..\bin\UnitTestFiles\UT_BaseMap.mdf \
         ..\..\bin\UnitTestFiles\UT_BaseMap.tsd \
         ..\..\bin\UnitTestFiles\UT_XYZ.tsd \
+        ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd \
+        ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd \
+        ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd \
         ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf \
         ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf \
         ..\..\bin\UnitTestFiles\UT_Parcels.fs \
@@ -720,6 +726,9 @@
           ..\..\bin\UnitTestFiles\UT_BaseMap.mdf \
           ..\..\bin\UnitTestFiles\UT_BaseMap.tsd \
           ..\..\bin\UnitTestFiles\UT_XYZ.tsd \
+          ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd \
+          ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd \
+          ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd \
           ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf \
           ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf \
           ..\..\bin\UnitTestFiles\UT_Parcels.fs \
@@ -997,6 +1006,9 @@
           ..\..\bin\UnitTestFiles\UT_BaseMap.mdf \
           ..\..\bin\UnitTestFiles\UT_BaseMap.tsd \
           ..\..\bin\UnitTestFiles\UT_XYZ.tsd \
+          ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd \
+          ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd \
+          ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd \
           ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf \
           ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf \
           ..\..\bin\UnitTestFiles\UT_Parcels.fs \
@@ -1146,7 +1158,10 @@
     if EXIST ..\..\bin\UnitTestFiles\TEST.sdf                       del /F ..\..\bin\UnitTestFiles\TEST.sdf
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.mdf                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.tsd                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.tsd
-    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ.tsd                        del /F ..\..\bin\UnitTestFiles\UT_XYZ.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ.tsd                     del /F ..\..\bin\UnitTestFiles\UT_XYZ.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd        del /F ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd       del /F ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd           del /F ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd
     if EXIST ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf           del /F ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf        del /F ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_Parcels.fs                  del /F ..\..\bin\UnitTestFiles\UT_Parcels.fs
@@ -1292,6 +1307,9 @@
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.mdf                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.tsd                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.tsd
     if EXIST ..\..\bin\UnitTestFiles\UT_XYZ.tsd                     del /F ..\..\bin\UnitTestFiles\UT_XYZ.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd        del /F ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd       del /F ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd           del /F ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd
     if EXIST ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf           del /F ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf        del /F ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_Parcels.fs                  del /F ..\..\bin\UnitTestFiles\UT_Parcels.fs
@@ -1437,6 +1455,9 @@
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.mdf                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.tsd                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.tsd
     if EXIST ..\..\bin\UnitTestFiles\UT_XYZ.tsd                     del /F ..\..\bin\UnitTestFiles\UT_XYZ.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd        del /F ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd       del /F ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd           del /F ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd
     if EXIST ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf           del /F ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf        del /F ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_Parcels.fs                  del /F ..\..\bin\UnitTestFiles\UT_Parcels.fs
@@ -1580,6 +1601,9 @@
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.mdf                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.tsd                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.tsd
     if EXIST ..\..\bin\UnitTestFiles\UT_XYZ.tsd                     del /F ..\..\bin\UnitTestFiles\UT_XYZ.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd        del /F ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd       del /F ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd           del /F ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd
     if EXIST ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf           del /F ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf        del /F ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_Parcels.fs                  del /F ..\..\bin\UnitTestFiles\UT_Parcels.fs
@@ -1850,6 +1874,9 @@
         ..\..\bin\UnitTestFiles\UT_BaseMap.mdf \
         ..\..\bin\UnitTestFiles\UT_BaseMap.tsd \
         ..\..\bin\UnitTestFiles\UT_XYZ.tsd \
+        ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd \
+        ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd \
+        ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd \
         ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf \
         ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf \
         ..\..\bin\UnitTestFiles\UT_Parcels.fs \
@@ -2120,6 +2147,9 @@
         ..\..\bin\UnitTestFiles\UT_BaseMap.mdf \
         ..\..\bin\UnitTestFiles\UT_BaseMap.tsd \
         ..\..\bin\UnitTestFiles\UT_XYZ.tsd \
+        ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd \
+        ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd \
+        ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd \
         ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf \
         ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf \
         ..\..\bin\UnitTestFiles\UT_Parcels.fs \
@@ -2390,6 +2420,9 @@
           ..\..\bin\UnitTestFiles\UT_BaseMap.mdf \
           ..\..\bin\UnitTestFiles\UT_BaseMap.tsd \
           ..\..\bin\UnitTestFiles\UT_XYZ.tsd \
+          ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd \
+          ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd \
+          ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd \
           ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf \
           ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf \
           ..\..\bin\UnitTestFiles\UT_Parcels.fs \
@@ -2660,6 +2693,9 @@
           ..\..\bin\UnitTestFiles\UT_BaseMap.mdf \
           ..\..\bin\UnitTestFiles\UT_BaseMap.tsd \
           ..\..\bin\UnitTestFiles\UT_XYZ.tsd \
+          ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd \
+          ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd \
+          ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd \
           ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf \
           ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf \
           ..\..\bin\UnitTestFiles\UT_Parcels.fs \
@@ -2802,7 +2838,10 @@
     if EXIST ..\..\bin\UnitTestFiles\TEST.sdf                       del /F ..\..\bin\UnitTestFiles\TEST.sdf
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.mdf                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.tsd                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.tsd
-    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ.tsd                        del /F ..\..\bin\UnitTestFiles\UT_XYZ.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ.tsd                     del /F ..\..\bin\UnitTestFiles\UT_XYZ.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd        del /F ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd       del /F ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd           del /F ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd
     if EXIST ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf           del /F ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf        del /F ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_Parcels.fs                  del /F ..\..\bin\UnitTestFiles\UT_Parcels.fs
@@ -2940,7 +2979,10 @@
     if EXIST ..\..\bin\UnitTestFiles\TEST.sdf                       del /F ..\..\bin\UnitTestFiles\TEST.sdf
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.mdf                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.tsd                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.tsd
-    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ.tsd                        del /F ..\..\bin\UnitTestFiles\UT_XYZ.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ.tsd                     del /F ..\..\bin\UnitTestFiles\UT_XYZ.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd        del /F ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd       del /F ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd           del /F ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd
     if EXIST ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf           del /F ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf        del /F ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_Parcels.fs                  del /F ..\..\bin\UnitTestFiles\UT_Parcels.fs
@@ -3078,7 +3120,10 @@
     if EXIST ..\..\bin\UnitTestFiles\TEST.sdf                       del /F ..\..\bin\UnitTestFiles\TEST.sdf
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.mdf                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.tsd                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.tsd
-    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ.tsd                        del /F ..\..\bin\UnitTestFiles\UT_XYZ.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ.tsd                     del /F ..\..\bin\UnitTestFiles\UT_XYZ.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd        del /F ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd       del /F ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd           del /F ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd
     if EXIST ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf           del /F ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf        del /F ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_Parcels.fs                  del /F ..\..\bin\UnitTestFiles\UT_Parcels.fs
@@ -3214,7 +3259,10 @@
     if EXIST ..\..\bin\UnitTestFiles\TEST.sdf                       del /F ..\..\bin\UnitTestFiles\TEST.sdf
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.mdf                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap.tsd                 del /F ..\..\bin\UnitTestFiles\UT_BaseMap.tsd
-    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ.tsd                        del /F ..\..\bin\UnitTestFiles\UT_XYZ.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ.tsd                     del /F ..\..\bin\UnitTestFiles\UT_XYZ.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd        del /F ..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd       del /F ..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd
+    if EXIST ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd           del /F ..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd
     if EXIST ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf           del /F ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf        del /F ..\..\bin\UnitTestFiles\UT_StylizationFuncs.mdf
     if EXIST ..\..\bin\UnitTestFiles\UT_Parcels.fs                  del /F ..\..\bin\UnitTestFiles\UT_Parcels.fs
@@ -3363,6 +3411,9 @@
 "..\..\..\UnitTest\TestData\TileService\UT_BaseMap.mdf" :
 "..\..\..\UnitTest\TestData\TileService\UT_BaseMap.tsd" :
 "..\..\..\UnitTest\TestData\TileService\UT_XYZ.tsd" :
+"..\..\..\UnitTest\TestData\TileService\UT_BaseMap_OrigSize.tsd" :
+"..\..\..\UnitTest\TestData\TileService\UT_BaseMap_Metatiled.tsd" :
+"..\..\..\UnitTest\TestData\TileService\UT_XYZ_Metatiled.tsd" :
 "..\..\..\UnitTest\TestData\TileService\UT_LinkedTileSet.mdf" :
 "..\..\..\UnitTest\TestData\TileService\UT_StylizationFuncs.mdf" :
 "..\..\..\UnitTest\TestData\TileService\UT_Parcels.fs" :
@@ -3631,6 +3682,18 @@
     if NOT EXIST ..\..\bin\UnitTestFiles\nul mkdir ..\..\bin\UnitTestFiles
     if EXIST "..\..\..\UnitTest\TestData\TileService\UT_XYZ.tsd" xcopy /r /d /y "..\..\..\UnitTest\TestData\TileService\UT_XYZ.tsd" ..\..\bin\UnitTestFiles\
 
+..\..\bin\UnitTestFiles\UT_BaseMap_OrigSize.tsd : "..\..\..\UnitTest\TestData\TileService\UT_BaseMap_OrigSize.tsd"
+    if NOT EXIST ..\..\bin\UnitTestFiles\nul mkdir ..\..\bin\UnitTestFiles
+    if EXIST "..\..\..\UnitTest\TestData\TileService\UT_BaseMap_OrigSize.tsd" xcopy /r /d /y "..\..\..\UnitTest\TestData\TileService\UT_BaseMap_OrigSize.tsd" ..\..\bin\UnitTestFiles\
+
+..\..\bin\UnitTestFiles\UT_BaseMap_Metatiled.tsd : "..\..\..\UnitTest\TestData\TileService\UT_BaseMap_Metatiled.tsd"
+    if NOT EXIST ..\..\bin\UnitTestFiles\nul mkdir ..\..\bin\UnitTestFiles
+    if EXIST "..\..\..\UnitTest\TestData\TileService\UT_BaseMap_Metatiled.tsd" xcopy /r /d /y "..\..\..\UnitTest\TestData\TileService\UT_BaseMap_Metatiled.tsd" ..\..\bin\UnitTestFiles\
+
+..\..\bin\UnitTestFiles\UT_XYZ_Metatiled.tsd : "..\..\..\UnitTest\TestData\TileService\UT_XYZ_Metatiled.tsd"
+    if NOT EXIST ..\..\bin\UnitTestFiles\nul mkdir ..\..\bin\UnitTestFiles
+    if EXIST "..\..\..\UnitTest\TestData\TileService\UT_XYZ_Metatiled.tsd" xcopy /r /d /y "..\..\..\UnitTest\TestData\TileService\UT_XYZ_Metatiled.tsd" ..\..\bin\UnitTestFiles\
+
 ..\..\bin\UnitTestFiles\UT_LinkedTileSet.mdf : "..\..\..\UnitTest\TestData\TileService\UT_LinkedTileSet.mdf"
     if NOT EXIST ..\..\bin\UnitTestFiles\nul mkdir ..\..\bin\UnitTestFiles
     if EXIST "..\..\..\UnitTest\TestData\TileService\UT_LinkedTileSet.mdf" xcopy /r /d /y "..\..\..\UnitTest\TestData\TileService\UT_LinkedTileSet.mdf" ..\..\bin\UnitTestFiles\

Modified: trunk/MgDev/Server/src/Services/Mapping/MappingUtil.h
===================================================================
--- trunk/MgDev/Server/src/Services/Mapping/MappingUtil.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Mapping/MappingUtil.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -38,6 +38,7 @@
 class RSMgFeatureReader;
 class TransformCache;
 class SE_SymbolManager;
+class Stylizer;
 
 namespace MdfModel
 {

Copied: trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatile.cpp (from rev 9509, sandbox/jng/tiling_v2/Server/src/Services/Rendering/OpRenderMetatile.cpp)
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatile.cpp	                        (rev 0)
+++ trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatile.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -0,0 +1,131 @@
+//
+//  Copyright (C) 2004-2019 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 "OpRenderMetatile.h"
+#include "LogManager.h"
+
+MgOpRenderMetatile::MgOpRenderMetatile()
+{
+}
+
+MgOpRenderMetatile::~MgOpRenderMetatile()
+{
+}
+
+void MgOpRenderMetatile::Execute()
+{
+    ACE_DEBUG((LM_DEBUG, ACE_TEXT("  (%t) MgOpRenderMetatile::Execute()\n")));
+
+    MG_LOG_OPERATION_MESSAGE(L"MgOpRenderMetatile");
+
+    MG_TRY()
+
+    MG_LOG_OPERATION_MESSAGE_INIT(m_packet.m_OperationVersion, m_packet.m_NumArguments);
+
+    ACE_ASSERT(m_stream != NULL);
+
+    if (10 == m_packet.m_NumArguments)
+    {
+        Ptr<MgMap> map = (MgMap*)m_stream->GetObject();
+        Ptr<MgResourceIdentifier> resource = map->GetResourceId();
+        map->SetDelayedLoadResourceService(m_resourceService);
+
+        STRING baseMapLayerGroupName;
+        m_stream->GetString(baseMapLayerGroupName);
+
+        INT32 tileCol = 0;
+        m_stream->GetInt32(tileCol);
+
+        INT32 tileRow = 0;
+        m_stream->GetInt32(tileRow);
+
+        INT32 tileWidth = 0;
+        m_stream->GetInt32(tileWidth);
+
+        INT32 tileHeight = 0;
+        m_stream->GetInt32(tileHeight);
+
+        INT32 tileDpi = 0;
+        m_stream->GetInt32(tileDpi);
+
+        STRING tileImageFormat;
+        m_stream->GetString(tileImageFormat);
+
+        double tileExtentOffset = 0.0;
+        m_stream->GetDouble(tileExtentOffset);
+
+        INT32 metaTileFactor = 0;
+        m_stream->GetInt32(metaTileFactor);
+
+        BeginExecution();
+
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_START();
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING((NULL == resource) ? L"MgResourceIdentifier" : resource->ToString().c_str());
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING(baseMapLayerGroupName.c_str());
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(tileCol);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(tileRow);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(tileWidth);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(tileHeight);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(tileDpi);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING(tileImageFormat.c_str());
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_DOUBLE(tileExtentOffset);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(metaTileFactor);
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_END();
+
+        Validate();
+
+        Ptr<MgMetatile> metaTile = m_service->RenderMetatile(map, baseMapLayerGroupName, tileCol, tileRow, tileWidth, tileHeight, tileDpi, tileImageFormat, tileExtentOffset, metaTileFactor);
+
+        EndExecution(metaTile);
+    }
+    else
+    {
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_START();
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_END();
+    }
+
+    if (!m_argsRead)
+    {
+        throw new MgOperationProcessingException(L"MgOpRenderMetatile.Execute",
+            __LINE__, __WFILE__, NULL, L"", NULL);
+    }
+
+    // Successful operation
+    MG_LOG_OPERATION_MESSAGE_ADD_STRING(MgResources::Success.c_str());
+
+    MG_CATCH(L"MgOpRenderMetatile.Execute")
+
+    if (mgException != NULL)
+    {
+        // Failed operation
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING(MgResources::Failure.c_str());
+    }
+
+    // Add access log entry for operation
+    MG_LOG_OPERATION_MESSAGE_ACCESS_ENTRY();
+
+    MG_THROW()
+}

Copied: trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatile.h (from rev 9509, sandbox/jng/tiling_v2/Server/src/Services/Rendering/OpRenderMetatile.h)
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatile.h	                        (rev 0)
+++ trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatile.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -0,0 +1,32 @@
+//
+//  Copyright (C) 2004-2019 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
+
+#ifndef MG_OP_RENDER_METATILE_H
+#define MG_OP_RENDER_METATILE_H
+
+#include "RenderingOperation.h"
+
+class MgOpRenderMetatile : public MgRenderingOperation
+{
+public:
+    MgOpRenderMetatile();
+    virtual ~MgOpRenderMetatile();
+
+public:
+    virtual void Execute();
+};
+
+#endif
\ No newline at end of file

Copied: trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatileXYZ.cpp (from rev 9509, sandbox/jng/tiling_v2/Server/src/Services/Rendering/OpRenderMetatileXYZ.cpp)
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatileXYZ.cpp	                        (rev 0)
+++ trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatileXYZ.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -0,0 +1,126 @@
+//
+//  Copyright (C) 2004-2019 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 "OpRenderMetatileXYZ.h"
+#include "LogManager.h"
+
+MgOpRenderMetatileXYZ::MgOpRenderMetatileXYZ()
+{
+}
+
+MgOpRenderMetatileXYZ::~MgOpRenderMetatileXYZ()
+{
+}
+
+void MgOpRenderMetatileXYZ::Execute()
+{
+    ACE_DEBUG((LM_DEBUG, ACE_TEXT("  (%t) MgOpRenderMetatileXYZ::Execute()\n")));
+
+    MG_LOG_OPERATION_MESSAGE(L"MgOpRenderMetatileXYZ");
+
+    MG_TRY()
+
+    MG_LOG_OPERATION_MESSAGE_INIT(m_packet.m_OperationVersion, m_packet.m_NumArguments);
+
+    ACE_ASSERT(m_stream != NULL);
+
+    if (8 == m_packet.m_NumArguments)
+    {
+        Ptr<MgMap> map = (MgMap*)m_stream->GetObject();
+        Ptr<MgResourceIdentifier> resource = map->GetResourceId();
+        map->SetDelayedLoadResourceService(m_resourceService);
+
+        STRING baseMapLayerGroupName;
+        m_stream->GetString(baseMapLayerGroupName);
+
+        INT32 x = 0;
+        m_stream->GetInt32(x);
+
+        INT32 y = 0;
+        m_stream->GetInt32(y);
+
+        INT32 z = 0;
+        m_stream->GetInt32(z);
+
+        INT32 tileDpi = 0;
+        m_stream->GetInt32(tileDpi);
+
+        STRING tileImageFormat;
+        m_stream->GetString(tileImageFormat);
+
+        double tileExtentOffset = 0.0;
+        m_stream->GetDouble(tileExtentOffset);
+
+        INT32 metaTilingFactor = 0;
+        m_stream->GetInt32(metaTilingFactor);
+
+        BeginExecution();
+
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_START();
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING((NULL == resource) ? L"MgResourceIdentifier" : resource->ToString().c_str());
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING(baseMapLayerGroupName.c_str());
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(x);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(y);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(z);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(tileDpi);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING(tileImageFormat.c_str());
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_DOUBLE(tileExtentOffset);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(metaTilingFactor);
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_END();
+
+        Validate();
+
+        Ptr<MgMetatile> metaTile = m_service->RenderMetatileXYZ(map, baseMapLayerGroupName, x, y, z, tileDpi, tileImageFormat, tileExtentOffset, metaTilingFactor);
+
+        EndExecution(metaTile);
+    }
+    else
+    {
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_START();
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_END();
+    }
+
+    if (!m_argsRead)
+    {
+        throw new MgOperationProcessingException(L"MgOpRenderMetatileXYZ.Execute",
+            __LINE__, __WFILE__, NULL, L"", NULL);
+    }
+
+    // Successful operation
+    MG_LOG_OPERATION_MESSAGE_ADD_STRING(MgResources::Success.c_str());
+
+    MG_CATCH(L"MgOpRenderMetatileXYZ.Execute")
+
+    if (mgException != NULL)
+    {
+        // Failed operation
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING(MgResources::Failure.c_str());
+    }
+
+    // Add access log entry for operation
+    MG_LOG_OPERATION_MESSAGE_ACCESS_ENTRY();
+
+    MG_THROW()
+}

Copied: trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatileXYZ.h (from rev 9509, sandbox/jng/tiling_v2/Server/src/Services/Rendering/OpRenderMetatileXYZ.h)
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatileXYZ.h	                        (rev 0)
+++ trunk/MgDev/Server/src/Services/Rendering/OpRenderMetatileXYZ.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -0,0 +1,32 @@
+//
+//  Copyright (C) 2004-2019 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
+
+#ifndef MG_OP_RENDER_METATILE_XYZ_H
+#define MG_OP_RENDER_METATILE_XYZ_H
+
+#include "RenderingOperation.h"
+
+class MgOpRenderMetatileXYZ : public MgRenderingOperation
+{
+public:
+    MgOpRenderMetatileXYZ();
+    virtual ~MgOpRenderMetatileXYZ();
+
+public:
+    virtual void Execute();
+};
+
+#endif
\ No newline at end of file

Modified: trunk/MgDev/Server/src/Services/Rendering/OpRenderTile.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/OpRenderTile.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Rendering/OpRenderTile.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -148,6 +148,65 @@
 
         EndExecution(byteReader);
     }
+    else if (9 == m_packet.m_NumArguments)
+    {
+        Ptr<MgMap> map = (MgMap*)m_stream->GetObject();
+        Ptr<MgResourceIdentifier> resource = map->GetResourceId();
+        map->SetDelayedLoadResourceService(m_resourceService);
+
+        STRING baseMapLayerGroupName;
+        m_stream->GetString(baseMapLayerGroupName);
+
+        INT32 tileCol = 0;
+        m_stream->GetInt32(tileCol);
+
+        INT32 tileRow = 0;
+        m_stream->GetInt32(tileRow);
+
+        INT32 tileWidth = 0;
+        m_stream->GetInt32(tileWidth);
+
+        INT32 tileHeight = 0;
+        m_stream->GetInt32(tileHeight);
+
+        INT32 tileDpi = 0;
+        m_stream->GetInt32(tileDpi);
+
+        STRING tileImageFormat;
+        m_stream->GetString(tileImageFormat);
+
+        double tileExtentOffset = 0.0;
+        m_stream->GetDouble(tileExtentOffset);
+
+        BeginExecution();
+
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_START();
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING((NULL == resource) ? L"MgResourceIdentifier" : resource->ToString().c_str());
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING(baseMapLayerGroupName.c_str());
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(tileCol);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(tileRow);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(tileWidth);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(tileHeight);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(tileDpi);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING(tileImageFormat.c_str());
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_DOUBLE(tileExtentOffset);
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_END();
+
+        Validate();
+
+        Ptr<MgByteReader> byteReader =
+            m_service->RenderTile(map, baseMapLayerGroupName, tileCol, tileRow, tileWidth, tileHeight, tileDpi, tileImageFormat, tileExtentOffset);
+
+        EndExecution(byteReader);
+    }
     else
     {
         MG_LOG_OPERATION_MESSAGE_PARAMETERS_START();

Copied: trunk/MgDev/Server/src/Services/Rendering/OpRenderTileFromMetatile.cpp (from rev 9509, sandbox/jng/tiling_v2/Server/src/Services/Rendering/OpRenderTileFromMetatile.cpp)
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/OpRenderTileFromMetatile.cpp	                        (rev 0)
+++ trunk/MgDev/Server/src/Services/Rendering/OpRenderTileFromMetatile.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -0,0 +1,109 @@
+//
+//  Copyright (C) 2004-2019 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 "OpRenderTileFromMetatile.h"
+
+MgOpRenderTileFromMetatile::MgOpRenderTileFromMetatile()
+{
+}
+
+MgOpRenderTileFromMetatile::~MgOpRenderTileFromMetatile()
+{
+}
+
+void MgOpRenderTileFromMetatile::Execute()
+{
+    ACE_DEBUG((LM_DEBUG, ACE_TEXT("  (%t) MgOpRenderTileFromMetatile::Execute()\n")));
+
+    MG_LOG_OPERATION_MESSAGE(L"MgOpRenderTileFromMetatile");
+
+    MG_TRY()
+
+    MG_LOG_OPERATION_MESSAGE_INIT(m_packet.m_OperationVersion, m_packet.m_NumArguments);
+
+    ACE_ASSERT(m_stream != NULL);
+
+    if (5 == m_packet.m_NumArguments)
+    {
+        Ptr<MgMap> map = (MgMap*)m_stream->GetObject();
+        Ptr<MgResourceIdentifier> resource = map->GetResourceId();
+        map->SetDelayedLoadResourceService(m_resourceService);
+
+        Ptr<MgMetatile> metaTile = (MgMetatile*)m_stream->GetObject();
+
+        STRING rendererName;
+        m_stream->GetString(rendererName);
+
+        INT32 subTileX = 0;
+        m_stream->GetInt32(subTileX);
+
+        INT32 subTileY = 0;
+        m_stream->GetInt32(subTileY);
+
+        BeginExecution();
+
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_START();
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING((NULL == resource) ? L"MgResourceIdentifier" : resource->ToString().c_str());
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING(L"MgMetatile[");
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(metaTile->GetMetaTilingFactor());
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(metaTile->GetMetaTilingFactor());
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING(L"]");
+        
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING(rendererName.c_str());
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(subTileX);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(subTileY);
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_END();
+
+        Validate();
+
+        Ptr<MgByteReader> tile = m_service->RenderTileFromMetaTile(map, metaTile, rendererName, subTileX, subTileY);
+
+        EndExecution(tile);
+    }
+    else
+    {
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_START();
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_END();
+    }
+
+    if (!m_argsRead)
+    {
+        throw new MgOperationProcessingException(L"MgOpRenderTileFromMetatile.Execute",
+            __LINE__, __WFILE__, NULL, L"", NULL);
+    }
+
+    // Successful operation
+    MG_LOG_OPERATION_MESSAGE_ADD_STRING(MgResources::Success.c_str());
+
+    MG_CATCH(L"MgOpRenderTileFromMetatile.Execute")
+
+    if (mgException != NULL)
+    {
+        // Failed operation
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING(MgResources::Failure.c_str());
+    }
+
+    // Add access log entry for operation
+    MG_LOG_OPERATION_MESSAGE_ACCESS_ENTRY();
+
+    MG_THROW()
+}
\ No newline at end of file

Copied: trunk/MgDev/Server/src/Services/Rendering/OpRenderTileFromMetatile.h (from rev 9509, sandbox/jng/tiling_v2/Server/src/Services/Rendering/OpRenderTileFromMetatile.h)
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/OpRenderTileFromMetatile.h	                        (rev 0)
+++ trunk/MgDev/Server/src/Services/Rendering/OpRenderTileFromMetatile.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -0,0 +1,32 @@
+//
+//  Copyright (C) 2004-2019 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
+
+#ifndef MG_OP_RENDER_TILE_FROM_METATILE_H
+#define MG_OP_RENDER_TILE_FROM_METATILE_H
+
+#include "RenderingOperation.h"
+
+class MgOpRenderTileFromMetatile : public MgRenderingOperation
+{
+public:
+    MgOpRenderTileFromMetatile();
+    virtual ~MgOpRenderTileFromMetatile();
+
+public:
+    virtual void Execute();
+};
+
+#endif
\ No newline at end of file

Modified: trunk/MgDev/Server/src/Services/Rendering/OpRenderTileXYZ.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/OpRenderTileXYZ.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Rendering/OpRenderTileXYZ.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -146,6 +146,59 @@
 
         EndExecution(byteReader);
     }
+    else if (8 == m_packet.m_NumArguments)
+    {
+        Ptr<MgMap> map = (MgMap*)m_stream->GetObject();
+        Ptr<MgResourceIdentifier> resource = map->GetResourceId();
+        map->SetDelayedLoadResourceService(m_resourceService);
+
+        STRING baseMapLayerGroupName;
+        m_stream->GetString(baseMapLayerGroupName);
+
+        INT32 x = 0;
+        m_stream->GetInt32(x);
+
+        INT32 y = 0;
+        m_stream->GetInt32(y);
+
+        INT32 z = 0;
+        m_stream->GetInt32(z);
+
+        INT32 tileDpi = 0;
+        m_stream->GetInt32(tileDpi);
+
+        STRING tileImageFormat;
+        m_stream->GetString(tileImageFormat);
+
+        double tileExtentOffset = 0.0;
+        m_stream->GetDouble(tileExtentOffset);
+
+        BeginExecution();
+
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_START();
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING((NULL == resource) ? L"MgResourceIdentifier" : resource->ToString().c_str());
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING(baseMapLayerGroupName.c_str());
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(x);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(y);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(z);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_INT32(tileDpi);
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_STRING(tileImageFormat.c_str());
+        MG_LOG_OPERATION_MESSAGE_ADD_SEPARATOR();
+        MG_LOG_OPERATION_MESSAGE_ADD_DOUBLE(tileExtentOffset);
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_END();
+
+        Validate();
+
+        Ptr<MgByteReader> byteReader = m_service->RenderTileXYZ(map, baseMapLayerGroupName, x, y, z, tileDpi, tileImageFormat, tileExtentOffset);
+
+        EndExecution(byteReader);
+    }
     else
     {
         MG_LOG_OPERATION_MESSAGE_PARAMETERS_START();

Modified: trunk/MgDev/Server/src/Services/Rendering/RenderingOperationFactory.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/RenderingOperationFactory.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Rendering/RenderingOperationFactory.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -108,6 +108,28 @@
                 L"MgRenderingOperationFactory.GetOperation", __LINE__, __WFILE__, NULL, L"", NULL);
         }
         break;
+    case MgRenderingServiceOpId::RenderMetatile:
+        switch (VERSION_NO_PHASE(operationVersion))
+        {
+        case VERSION_SUPPORTED(3, 3):
+            handler.reset(new MgOpRenderMetatile());
+            break;
+        default:
+            throw new MgInvalidOperationVersionException(
+                L"MgRenderingOperationFactory.GetOperation", __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+        break;
+    case MgRenderingServiceOpId::RenderTileFromMetaTile:
+        switch (VERSION_NO_PHASE(operationVersion))
+        {
+        case VERSION_SUPPORTED(3, 3):
+            handler.reset(new MgOpRenderTileFromMetatile());
+            break;
+        default:
+            throw new MgInvalidOperationVersionException(
+                L"MgRenderingOperationFactory.GetOperation", __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+        break;
     case MgRenderingServiceOpId::RenderTileUTFGrid:
         switch (VERSION_NO_PHASE(operationVersion))
         {

Modified: trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -45,11 +45,6 @@
 static const INT32 FILTER_SELECTABLE = 2;
 static const INT32 FILTER_HASTOOLTIPS = 4;
 
-inline bool hasColorMap (STRING format)
-{
-    return format == L"PNG8" || format == L"GIF";
-}
-
 IMPLEMENT_CREATE_SERVICE(MgServerRenderingService)
 
 
@@ -179,65 +174,20 @@
 {
     Ptr<MgByteReader> ret;
 
+    // the label renderer needs to know the tile extent offset parameter
+    double tileExtentOffset = 0.0;
+    MgConfiguration* pConf = MgConfiguration::GetInstance();
+    pConf->GetDoubleValue(MgConfigProperties::RenderingServicePropertiesSection,
+        MgConfigProperties::RenderingServicePropertyTileExtentOffset,
+        tileExtentOffset,
+        MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset);
+    if (tileExtentOffset < 0.0)
+        tileExtentOffset = MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset;
+
     MG_TRY()
 
-    CHECKARGUMENTNULL(map, L"MgServerRenderingService.RenderTile");
-    CHECKARGUMENTEMPTYSTRING(baseMapLayerGroupName, L"MgServerRenderingService.RenderTile");
+    ret = RenderTile(map, baseMapLayerGroupName, tileColumn, tileRow, tileWidth, tileHeight, tileDpi, tileImageFormat, tileExtentOffset);
 
-    // find the finite display scale closest to the requested map scale
-    double scale = map->GetViewScale();
-    INT32 scaleIndex = map->FindNearestFiniteDisplayScaleIndex(scale);
-
-    // if we don't find a nearest scale then something is wrong with the map
-    if (scaleIndex < 0)
-        throw new MgInvalidMapDefinitionException(L"MgServerRenderingService.RenderTile", __LINE__, __WFILE__, NULL, L"", NULL);
-
-    // get the layer group associated with the name
-    Ptr<MgLayerGroupCollection> layerGroups = map->GetLayerGroups();
-    Ptr<MgLayerGroup> baseGroup = layerGroups->GetItem(baseMapLayerGroupName);
-    if (baseGroup == NULL)
-    {
-        MgStringCollection arguments;
-        arguments.Add(L"2");
-        arguments.Add(baseMapLayerGroupName);
-
-        throw new MgInvalidArgumentException(L"MgServerRenderingService.RenderTile",
-            __LINE__, __WFILE__, &arguments, L"MgMapLayerGroupNameNotFound", NULL);
-    }
-
-    // get the scale at which to render the tile
-    scale = map->GetFiniteDisplayScaleAt(scaleIndex);
-
-    // ensure the tile DPI is set on the map
-    map->SetDisplayDpi(tileDpi);
-
-    // ------------------------------------------------------
-    // the upper left corner of tile (0,0) corresponds to the
-    // upper left corner of the map extent
-    // ------------------------------------------------------
-
-    Ptr<MgEnvelope> mapExtent = map->GetMapExtent();
-    Ptr<MgCoordinate> pt00 = mapExtent->GetLowerLeftCoordinate();
-    Ptr<MgCoordinate> pt11 = mapExtent->GetUpperRightCoordinate();
-    double mapMinX = rs_min(pt00->GetX(), pt11->GetX());
-    double mapMaxX = rs_max(pt00->GetX(), pt11->GetX());
-    double mapMinY = rs_min(pt00->GetY(), pt11->GetY());
-    double mapMaxY = rs_max(pt00->GetY(), pt11->GetY());
-
-    double metersPerUnit  = map->GetMetersPerUnit();
-    double metersPerPixel = METERS_PER_INCH / tileDpi;
-    double tileWidthMCS   = (double)tileWidth  * metersPerPixel * scale / metersPerUnit;
-    double tileHeightMCS  = (double)tileHeight * metersPerPixel * scale / metersPerUnit;
-
-    double tileMinX = mapMinX + (double)(tileColumn  ) * tileWidthMCS;  // left edge
-    double tileMaxX = mapMinX + (double)(tileColumn+1) * tileWidthMCS;  // right edge
-    double tileMinY = mapMaxY - (double)(tileRow   +1) * tileHeightMCS; // bottom edge
-    double tileMaxY = mapMaxY - (double)(tileRow     ) * tileHeightMCS; // top edge
-
-    // make the call to render the tile
-    ret = RenderTile(map, baseGroup, scaleIndex, tileWidth, tileHeight, scale,
-                     tileMinX, tileMaxX, tileMinY, tileMaxY, tileImageFormat);
-
     MG_CATCH_AND_THROW(L"MgServerRenderingService.RenderTile")
 
     return ret.Detach();
@@ -312,123 +262,20 @@
 {
     Ptr<MgByteReader> ret;
 
-    MG_TRY()
-
-    CHECKARGUMENTNULL(map, L"MgServerRenderingService.RenderTileXYZ");
-    CHECKARGUMENTEMPTYSTRING(baseMapLayerGroupName, L"MgServerRenderingService.RenderTileXYZ");
-    
-    // get the layer group associated with the name
-    Ptr<MgLayerGroupCollection> layerGroups = map->GetLayerGroups();
-    Ptr<MgLayerGroup> baseGroup = layerGroups->GetItem(baseMapLayerGroupName);
-    if (baseGroup == NULL)
-    {
-        MgStringCollection arguments;
-        arguments.Add(L"2");
-        arguments.Add(baseMapLayerGroupName);
-
-        throw new MgInvalidArgumentException(L"MgServerRenderingService.RenderTileXYZ",
-            __LINE__, __WFILE__, &arguments, L"MgMapLayerGroupNameNotFound", NULL);
-    }
-
-    //Set the dpi
-    map->SetDisplayDpi(dpi);
-
-    int width = XYZ_TILE_WIDTH;
-    int height = XYZ_TILE_HEIGHT;
-
-    // Inlining same logic from RenderTile() overload below as we want the same logic, but we want to pass scale
-    // instead of scale index
-    RS_Bounds extent;
-    ComputeXYZTileExtents(map, x, y, z, extent);
-
-    // use the map's background color, but always make it fully transparent
-    RS_Color bgColor;
-    StylizationUtil::ParseColor(map->GetBackgroundColor(), bgColor);
-    bgColor.alpha() = 0;
-
     // the label renderer needs to know the tile extent offset parameter
     double tileExtentOffset = 0.0;
     MgConfiguration* pConf = MgConfiguration::GetInstance();
     pConf->GetDoubleValue(MgConfigProperties::RenderingServicePropertiesSection,
-                          MgConfigProperties::RenderingServicePropertyTileExtentOffset,
-                          tileExtentOffset,
-                          MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset);
+        MgConfigProperties::RenderingServicePropertyTileExtentOffset,
+        tileExtentOffset,
+        MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset);
     if (tileExtentOffset < 0.0)
         tileExtentOffset = MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset;
-    
-    // Image scaling logic ripped verbatim from RenderMap() overload that takes MgEnvelope
-    //
-    // If we need to scale the image (because of request for non-square
-    // pixels) we will need to draw at one image size and then save at
-    // another scaled size.  Here we will compute the correct map scale
-    // and render size for a requested extent and image size.
-    double screenAR = (double)width / (double)height;
-    double mapAR = extent.width() / extent.height();
 
-    int drawWidth = width;
-    int drawHeight = height;
-    double scale = 0.0;
+    MG_TRY()
 
-    if (mapAR >= screenAR)
-    {
-        scale = extent.width() * map->GetMetersPerUnit() / METERS_PER_INCH * (double)dpi / (double)width;
+    ret = RenderTileXYZ(map, baseMapLayerGroupName, x, y, z, dpi, tileImageFormat, tileExtentOffset);
 
-        // we based map scale on the image width, so adjust rendering
-        // height to match the map aspect ratio
-        drawHeight = (int)(width / mapAR);
-
-        // ignore small perturbations in order to avoid rescaling the
-        // end image in cases where the rescaling of width is less than
-        // a pixel or so
-        if (abs(drawHeight - height) <= 1)
-            drawHeight = height;
-    }
-    else
-    {
-        scale = extent.height() * map->GetMetersPerUnit() / METERS_PER_INCH * (double)dpi / (double)height;
-
-        // we based map scale on the image height, so adjust rendering
-        // height to match the map aspect ratio
-        drawWidth = (int)(height * mapAR);
-
-        // ignore small perturbations, in order to avoid rescaling the
-        // end image in cases where the rescaling of width is less than
-        // a pixel or so
-        if (abs(drawWidth - width) <= 1)
-            drawWidth = width;
-    }
-
-    //printf("XYZ(%d, %d, %d) -> [%f, %f] [%f, %f] at %f -- (w: %d, h: %d, mpu: %f)\n", x, y, z, mcsMinX, mcsMinY, mcsMaxX, mcsMaxY, scale, width, height, map->GetMetersPerUnit());
-
-    // sanity check - number of image pixels cannot exceed MAX_PIXELS
-    if (drawWidth * drawHeight > MAX_PIXELS)
-        throw new MgOutOfRangeException(L"MgServerRenderingService.RenderMap", __LINE__, __WFILE__, NULL, L"MgInvalidImageSizeTooBig", NULL);
-
-    // initialize the renderer (set clipping to false so that we label
-    // the unclipped geometry)
-    auto_ptr<SE_Renderer> dr(CreateRenderer(drawWidth, drawHeight, bgColor, false, true, tileExtentOffset));
-
-    // create a temporary collection containing all the layers for the base group
-    Ptr<MgLayerCollection> layers = map->GetLayers();
-    Ptr<MgReadOnlyLayerCollection> roLayers = new MgReadOnlyLayerCollection();
-    for (int i=0; i<layers->GetCount(); i++)
-    {
-        Ptr<MgLayerBase> layer = layers->GetItem(i);
-        Ptr<MgLayerGroup> parentGroup = layer->GetGroup();
-        if (parentGroup == baseGroup)
-            roLayers->Add(layer);
-    }
-
-    // of course the group has to also be visible
-    bool groupVisible = baseGroup->GetVisible();
-    baseGroup->SetVisible(true);
-
-    // call the internal helper API to do all the stylization overhead work
-    ret = RenderMapInternal(map, NULL, roLayers, dr.get(), drawWidth, drawHeight, width, height, tileImageFormat, scale, extent, true, true, false, NULL);
-
-    // restore the base group's visibility
-    baseGroup->SetVisible(groupVisible);
-
     MG_CATCH_AND_THROW(L"MgServerRenderingService.RenderTileXYZ")
 
     return ret.Detach();
@@ -489,48 +336,11 @@
     if (tileExtentOffset < 0.0)
         tileExtentOffset = MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset;
 
-    // Image scaling logic ripped verbatim from RenderMap() overload that takes MgEnvelope
-    //
-    // If we need to scale the image (because of request for non-square
-    // pixels) we will need to draw at one image size and then save at
-    // another scaled size.  Here we will compute the correct map scale
-    // and render size for a requested extent and image size.
-    double screenAR = (double)width / (double)height;
-    double mapAR = extent.width() / extent.height();
-
-    int drawWidth = width;
-    int drawHeight = height;
+    INT32 drawWidth = width;
+    INT32 drawHeight = height;
     double scale = 0.0;
+    ComputeScaledDimensions(extent, width, height, dpi, map->GetMetersPerUnit(), drawWidth, drawHeight, scale);
 
-    if (mapAR >= screenAR)
-    {
-        scale = extent.width() * map->GetMetersPerUnit() / METERS_PER_INCH * (double)dpi / (double)width;
-
-        // we based map scale on the image width, so adjust rendering
-        // height to match the map aspect ratio
-        drawHeight = (int)(width / mapAR);
-
-        // ignore small perturbations in order to avoid rescaling the
-        // end image in cases where the rescaling of width is less than
-        // a pixel or so
-        if (abs(drawHeight - height) <= 1)
-            drawHeight = height;
-    }
-    else
-    {
-        scale = extent.height() * map->GetMetersPerUnit() / METERS_PER_INCH * (double)dpi / (double)height;
-
-        // we based map scale on the image height, so adjust rendering
-        // height to match the map aspect ratio
-        drawWidth = (int)(height * mapAR);
-
-        // ignore small perturbations, in order to avoid rescaling the
-        // end image in cases where the rescaling of width is less than
-        // a pixel or so
-        if (abs(drawWidth - width) <= 1)
-            drawWidth = width;
-    }
-
     //printf("XYZ(%d, %d, %d) -> [%f, %f] [%f, %f] at %f -- (w: %d, h: %d, mpu: %f)\n", x, y, z, mcsMinX, mcsMinY, mcsMaxX, mcsMaxY, scale, width, height, map->GetMetersPerUnit());
 
     // sanity check - number of image pixels cannot exceed MAX_PIXELS
@@ -626,17 +436,18 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 /// render a map using all layers from the baseGroup
-MgByteReader* MgServerRenderingService::RenderTile(MgMap* map,
-                                                   MgLayerGroup* baseGroup,
-                                                   INT32 scaleIndex,
-                                                   INT32 width,
-                                                   INT32 height,
-                                                   double scale,
-                                                   double mcsMinX,
-                                                   double mcsMaxX,
-                                                   double mcsMinY,
-                                                   double mcsMaxY,
-                                                   CREFSTRING format)
+MgByteReader* MgServerRenderingService::RenderTileInternal(MgMap* map,
+                                                           MgLayerGroup* baseGroup,
+                                                           INT32 scaleIndex,
+                                                           INT32 width,
+                                                           INT32 height,
+                                                           double scale,
+                                                           double mcsMinX,
+                                                           double mcsMaxX,
+                                                           double mcsMinY,
+                                                           double mcsMaxY,
+                                                           CREFSTRING format,
+                                                           double tileExtentOffset)
 {
     Ptr<MgByteReader> ret;
 
@@ -652,19 +463,9 @@
     StylizationUtil::ParseColor(map->GetBackgroundColor(), bgColor);
     bgColor.alpha() = 0;
 
-    // the label renderer needs to know the tile extent offset parameter
-    double tileExtentOffset = 0.0;
-    MgConfiguration* pConf = MgConfiguration::GetInstance();
-    pConf->GetDoubleValue(MgConfigProperties::RenderingServicePropertiesSection,
-                          MgConfigProperties::RenderingServicePropertyTileExtentOffset,
-                          tileExtentOffset,
-                          MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset);
-    if (tileExtentOffset < 0.0)
-        tileExtentOffset = MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset;
-
     // initialize the renderer (set clipping to false so that we label
     // the unclipped geometry)
-    auto_ptr<SE_Renderer> dr(CreateRenderer(width, height, bgColor, false, true, tileExtentOffset));
+    auto_ptr<SE_Renderer> dr(CreateRenderer(m_rendererName, width, height, bgColor, false, true, tileExtentOffset));
 
     // create a temporary collection containing all the layers for the base group
     Ptr<MgLayerCollection> layers = map->GetLayers();
@@ -692,7 +493,6 @@
     return ret.Detach();
 }
 
-
 ///////////////////////////////////////////////////////////////////////////////
 // default arg bKeepSelection = true
 MgByteReader* MgServerRenderingService::RenderDynamicOverlay(MgMap* map,
@@ -788,7 +588,7 @@
     bgColor.alpha() = 0;
 
     // initialize the renderer
-    auto_ptr<SE_Renderer> dr(CreateRenderer(width, height, bgColor, true));
+    auto_ptr<SE_Renderer> dr(CreateRenderer(m_rendererName, width, height, bgColor, true));
 
     bool bIncludeDynamicLayers = ((options->GetBehavior() & MgRenderingOptions::RenderLayers) == MgRenderingOptions::RenderLayers);
     bool bIncludeBaseLayers = ((options->GetBehavior() & MgRenderingOptions::RenderBaseLayers) == MgRenderingOptions::RenderBaseLayers);
@@ -966,46 +766,11 @@
 
     RS_Bounds b(ll->GetX(), ll->GetY(), ur->GetX(), ur->GetY());
 
-    // If we need to scale the image (because of request for non-square
-    // pixels) we will need to draw at one image size and then save at
-    // another scaled size.  Here we will compute the correct map scale
-    // and render size for a requested extent and image size.
-    double screenAR = (double)width / (double)height;
-    double mapAR = b.width() / b.height();
-
-    int drawWidth = width;
-    int drawHeight = height;
+    INT32 drawWidth = width;
+    INT32 drawHeight = height;
     double scale = 0.0;
+    ComputeScaledDimensions(b, width, height, dpi, metersPerUnit, drawWidth, drawHeight, scale);
 
-    if (mapAR >= screenAR)
-    {
-        scale = b.width() * metersPerUnit / METERS_PER_INCH * (double)dpi / (double)width;
-
-        // we based map scale on the image width, so adjust rendering
-        // height to match the map aspect ratio
-        drawHeight = (int)(width / mapAR);
-
-        // ignore small perturbations in order to avoid rescaling the
-        // end image in cases where the rescaling of width is less than
-        // a pixel or so
-        if (abs(drawHeight - height) <= 1)
-            drawHeight = height;
-    }
-    else
-    {
-        scale = b.height() * metersPerUnit / METERS_PER_INCH * (double)dpi / (double)height;
-
-        // we based map scale on the image height, so adjust rendering
-        // height to match the map aspect ratio
-        drawWidth = (int)(height * mapAR);
-
-        // ignore small perturbations, in order to avoid rescaling the
-        // end image in cases where the rescaling of width is less than
-        // a pixel or so
-        if (abs(drawWidth - width) <= 1)
-            drawWidth = width;
-    }
-
     // sanity check - number of image pixels cannot exceed MAX_PIXELS
     if (drawWidth * drawHeight > MAX_PIXELS)
         throw new MgOutOfRangeException(L"MgServerRenderingService.RenderMap", __LINE__, __WFILE__, NULL, L"MgInvalidImageSizeTooBig", NULL);
@@ -1019,7 +784,7 @@
     // initialize the renderer with the rendering canvas size - in this
     // case it is not necessarily the same size as the requested image
     // size due to support for non-square pixels
-    auto_ptr<SE_Renderer> dr(CreateRenderer(drawWidth, drawHeight, bgcolor, false));
+    auto_ptr<SE_Renderer> dr(CreateRenderer(m_rendererName, drawWidth, drawHeight, bgcolor, false));
 
     // call the internal helper API to do all the stylization overhead work
     ret = RenderMapInternal(map, selection, NULL, dr.get(), drawWidth, drawHeight, width, height, format, scale, b, true, bKeepSelection, true, NULL);
@@ -1158,7 +923,7 @@
                      backgroundColor->GetAlpha());
 
     // initialize the appropriate map renderer
-    auto_ptr<SE_Renderer> dr(CreateRenderer(width, height, bgcolor, bClip));
+    auto_ptr<SE_Renderer> dr(CreateRenderer(m_rendererName, width, height, bgcolor, bClip));
 
     if(NULL != pPRMResult)
     {
@@ -1263,7 +1028,7 @@
                 point = point_buf;
 
                 RS_Color bgColor; // not used
-                impRenderer.reset(CreateRenderer(1, 1, bgColor, false));
+                impRenderer.reset(CreateRenderer(m_rendererName, 1, 1, bgColor, false));
             }
         }
     }
@@ -1524,7 +1289,7 @@
 
     MG_THROW()  // to skip a faulty tile we need to rethrow the exception which could be thrown in StylizeLayers
 
-    Ptr<MgByteReader> ret = CreateImage(map, dr, saveWidth, saveHeight, format, pPRMResult);
+    Ptr<MgByteReader> ret = CreateImageFromRenderer(map, dr, saveWidth, saveHeight, format, m_rendererName, pPRMResult);
 
     return ret.Detach();
 }
@@ -1560,7 +1325,7 @@
                      backgroundColor->GetAlpha());
 
     //initialize a renderer
-    auto_ptr<Renderer> dr(CreateRenderer(width, height, bgcolor, false, false, 0.0));
+    auto_ptr<Renderer> dr(CreateRenderer(m_rendererName, width, height, bgcolor, false, false, 0.0));
 
     RS_Bounds b(0,0,width,height);
 
@@ -2051,8 +1816,9 @@
 
 
 ///////////////////////////////////////////////////////////////////////////////
-SE_Renderer* MgServerRenderingService::CreateRenderer(int width,
-                                                      int height,
+SE_Renderer* MgServerRenderingService::CreateRenderer(CREFSTRING rendererName,
+                                                      INT32 width,
+                                                      INT32 height,
                                                       RS_Color& bgColor,
                                                       bool requiresClipping,
                                                       bool localOverposting,
@@ -2059,7 +1825,7 @@
                                                       double tileExtentOffset)
 {
     SE_Renderer* renderer = NULL;
-    if (wcscmp(m_rendererName.c_str(), L"AGG") == 0)
+    if (wcscmp(rendererName.c_str(), L"AGG") == 0)
         renderer = new AGGRenderer(width, height, bgColor, requiresClipping, localOverposting, tileExtentOffset);
     else
         renderer = new GDRenderer(width, height, bgColor, requiresClipping, localOverposting, tileExtentOffset);
@@ -2100,7 +1866,7 @@
 
     MgMappingUtil::StylizeLayers(m_svcResource, m_svcFeature, m_svcDrawing, m_pCSFactory, map,
                                     layers, NULL, ds, dr, dstCs, expandExtents, false, scale,
-                                    false, hasColorMap(format), pPRLsResult);
+                                    false, HasColorMap(format), pPRLsResult);
 
     if(NULL != pPRMResult)
     {
@@ -2450,41 +2216,415 @@
     }
 }
 
-////////////////////////////////////////////////////////////////////////////////
-inline MgByteReader* MgServerRenderingService::CreateImage(MgMap* map,
-                                                           Renderer* dr,
-                                                           INT32 saveWidth,
-                                                           INT32 saveHeight,
-                                                           CREFSTRING format,
-                                                           ProfileRenderMapResult* pPRMResult)
+MgByteReader* MgServerRenderingService::RenderTile(MgMap* map,
+                                                   CREFSTRING baseMapLayerGroupName,
+                                                   INT32 tileColumn,
+                                                   INT32 tileRow,
+                                                   INT32 tileWidth,
+                                                   INT32 tileHeight,
+                                                   INT32 tileDpi,
+                                                   CREFSTRING tileImageFormat,
+                                                   double tileExtentOffset)
 {
-    if(NULL != pPRMResult)
+    Ptr<MgByteReader> ret;
+
+    MG_TRY()
+
+    INT32 metaTilingFactor = 0;
+    Ptr<MgMetatile> metaTile = RenderMetatile(map, baseMapLayerGroupName, tileColumn, tileRow, tileWidth, tileHeight, tileDpi, tileImageFormat, tileExtentOffset, metaTilingFactor);
+
+    _ASSERT(metaTile->GetRequestedWidth() == tileWidth);
+    _ASSERT(metaTile->GetRequestedHeight() == tileHeight);
+    _ASSERT(metaTile->GetMetaTilingFactor() <= 1);
+
+    ret = metaTile->GetImage();
+
+    MG_CATCH_AND_THROW(L"MgServerRenderingService.RenderTile")
+
+    return ret.Detach();
+}
+
+MgByteReader* MgServerRenderingService::RenderTileXYZ(MgMap* map,
+                                                      CREFSTRING baseMapLayerGroupName,
+                                                      INT32 x,
+                                                      INT32 y,
+                                                      INT32 z,
+                                                      INT32 dpi,
+                                                      CREFSTRING tileImageFormat,
+                                                      double tileExtentOffset)
+{
+    Ptr<MgByteReader> ret;
+
+    MG_TRY()
+
+    INT32 metaTilingFactor = 0;
+    Ptr<MgMetatile> metaTile = RenderMetatileXYZ(map, baseMapLayerGroupName, x, y, z, dpi, tileImageFormat, tileExtentOffset, metaTilingFactor);
+
+    _ASSERT(metaTile->GetRequestedWidth() == XYZ_TILE_WIDTH);
+    _ASSERT(metaTile->GetRequestedHeight() == XYZ_TILE_HEIGHT);
+    _ASSERT(metaTile->GetMetaTilingFactor() <= 1);
+
+    ret = metaTile->GetImage();
+
+    MG_CATCH_AND_THROW(L"MgServerRenderingService.RenderTileXYZ")
+
+    return ret.Detach();
+}
+
+void MgServerRenderingService::ComputeScaledDimensions(RS_Bounds& extent, INT32 width, INT32 height, INT32 dpi,
+                                                       double metersPerUnit, INT32& drawWidth, INT32& drawHeight, 
+                                                       double& scale)
+{
+    // If we need to scale the image (because of request for non-square
+    // pixels) we will need to draw at one image size and then save at
+    // another scaled size.  Here we will compute the correct map scale
+    // and render size for a requested extent and image size.
+    double screenAR = (double)width / (double)height;
+    double mapAR = extent.width() / extent.height();
+
+    drawWidth = width;
+    drawHeight = height;
+    scale = 0.0;
+
+    if (mapAR >= screenAR)
     {
+        scale = extent.width() * metersPerUnit / METERS_PER_INCH * (double)dpi / (double)width;
+
+        // we based map scale on the image width, so adjust rendering
+        // height to match the map aspect ratio
+        drawHeight = (int)(width / mapAR);
+
+        // ignore small perturbations in order to avoid rescaling the
+        // end image in cases where the rescaling of width is less than
+        // a pixel or so
+        if (abs(drawHeight - height) <= 1)
+            drawHeight = height;
+    }
+    else
+    {
+        scale = extent.height() * metersPerUnit / METERS_PER_INCH * (double)dpi / (double)height;
+
+        // we based map scale on the image height, so adjust rendering
+        // height to match the map aspect ratio
+        drawWidth = (int)(height * mapAR);
+
+        // ignore small perturbations, in order to avoid rescaling the
+        // end image in cases where the rescaling of width is less than
+        // a pixel or so
+        if (abs(drawWidth - width) <= 1)
+            drawWidth = width;
+    }
+}
+
+
+MgMetatile* MgServerRenderingService::RenderMetatile(MgMap* map,
+                                                     CREFSTRING baseMapLayerGroupName,
+                                                     INT32 tileColumn,
+                                                     INT32 tileRow,
+                                                     INT32 tileWidth,
+                                                     INT32 tileHeight,
+                                                     INT32 tileDpi,
+                                                     CREFSTRING tileImageFormat,
+                                                     double tileExtentOffset,
+                                                     INT32 metaTileFactor)
+{
+    Ptr<MgMetatile> ret;
+
+    MG_TRY()
+
+    CHECKARGUMENTNULL(map, L"MgServerRenderingService.RenderMetatile");
+    CHECKARGUMENTEMPTYSTRING(baseMapLayerGroupName, L"MgServerRenderingService.RenderMetatile");
+
+    // find the finite display scale closest to the requested map scale
+    double scale = map->GetViewScale();
+    INT32 scaleIndex = map->FindNearestFiniteDisplayScaleIndex(scale);
+
+    // if we don't find a nearest scale then something is wrong with the map
+    if (scaleIndex < 0)
+        throw new MgInvalidMapDefinitionException(L"MgServerRenderingService.RenderMetatile", __LINE__, __WFILE__, NULL, L"", NULL);
+
+    // get the layer group associated with the name
+    Ptr<MgLayerGroupCollection> layerGroups = map->GetLayerGroups();
+    Ptr<MgLayerGroup> baseGroup = layerGroups->GetItem(baseMapLayerGroupName);
+    if (baseGroup == NULL)
+    {
+        MgStringCollection arguments;
+        arguments.Add(L"2");
+        arguments.Add(baseMapLayerGroupName);
+
+        throw new MgInvalidArgumentException(L"MgServerRenderingService.RenderMetatile",
+            __LINE__, __WFILE__, &arguments, L"MgMapLayerGroupNameNotFound", NULL);
+    }
+
+    // get the scale at which to render the tile
+    scale = map->GetFiniteDisplayScaleAt(scaleIndex);
+
+    // ensure the tile DPI is set on the map
+    map->SetDisplayDpi(tileDpi);
+
+    // ------------------------------------------------------
+    // the upper left corner of tile (0,0) corresponds to the
+    // upper left corner of the map extent
+    // ------------------------------------------------------
+
+    double tileMinX, tileMaxX, tileMinY, tileMaxY;
+    map->GetTileCoords(std::max(metaTileFactor, 1), tileColumn, tileRow, tileDpi, tileWidth, tileHeight, tileMinX, tileMaxX, tileMinY, tileMaxY);
+
+    // make the call to render the tile
+    INT32 tw = tileWidth * std::max(metaTileFactor, 1);
+    INT32 th = tileHeight * std::max(metaTileFactor, 1);
+    STRING tformat = (metaTileFactor > 1 ? MgImageFormats::Meta : tileImageFormat);
+    Ptr<MgByteReader> tile = RenderTileInternal(map, baseGroup, scaleIndex, tw, th,
+                                                scale, tileMinX, tileMaxX, tileMinY, tileMaxY, 
+                                                tformat, tileExtentOffset);
+
+    ret = new MgMetatile(tile, tw, th, tileWidth, tileHeight, tileImageFormat, metaTileFactor);
+
+    MG_CATCH_AND_THROW(L"MgServerRenderingService.RenderMetatile")
+
+    return ret.Detach();
+}
+
+MgMetatile* MgServerRenderingService::RenderMetatileXYZ(MgMap* map,
+                                                        CREFSTRING baseMapLayerGroupName,
+                                                        INT32 x,
+                                                        INT32 y,
+                                                        INT32 z,
+                                                        INT32 dpi,
+                                                        CREFSTRING tileImageFormat,
+                                                        double tileExtentOffset,
+                                                        INT32 metaTilingFactor)
+{
+    Ptr<MgMetatile> ret;
+
+    MG_TRY()
+
+    CHECKARGUMENTNULL(map, L"MgServerRenderingService.RenderMetatileXYZ");
+    CHECKARGUMENTEMPTYSTRING(baseMapLayerGroupName, L"MgServerRenderingService.RenderMetatileXYZ");
+    
+    // get the layer group associated with the name
+    Ptr<MgLayerGroupCollection> layerGroups = map->GetLayerGroups();
+    Ptr<MgLayerGroup> baseGroup = layerGroups->GetItem(baseMapLayerGroupName);
+    if (baseGroup == NULL)
+    {
+        MgStringCollection arguments;
+        arguments.Add(L"2");
+        arguments.Add(baseMapLayerGroupName);
+
+        throw new MgInvalidArgumentException(L"MgServerRenderingService.RenderMetatileXYZ",
+            __LINE__, __WFILE__, &arguments, L"MgMapLayerGroupNameNotFound", NULL);
+    }
+
+    //Set the dpi
+    map->SetDisplayDpi(dpi);
+
+    // Inlining same logic from RenderTile() overload below as we want the same logic, but we want to pass scale
+    // instead of scale index
+    RS_Bounds extent;
+    ComputeXYZTileExtents(map, x, y, z, extent);
+
+    // If meta-tiling, request bounds for:
+    //   (x, y, z) -> (x + (tm - 1), y + (tm - 1), z)
+    // And combine the resulting bounds
+    if (metaTilingFactor > 1)
+    {
+        RS_Bounds extent2;
+        INT32 endX = x + (metaTilingFactor - 1);
+        INT32 endY = y + (metaTilingFactor - 1);
+        ComputeXYZTileExtents(map, endX, endY, z, extent2);
+
+        extent.add_bounds(extent2);
+    }
+
+    // use the map's background color, but always make it fully transparent
+    RS_Color bgColor;
+    StylizationUtil::ParseColor(map->GetBackgroundColor(), bgColor);
+    bgColor.alpha() = 0;
+    
+    INT32 width = XYZ_TILE_WIDTH * std::max(metaTilingFactor, 1);
+    INT32 height = XYZ_TILE_HEIGHT * std::max(metaTilingFactor, 1);
+    INT32 drawWidth = width;
+    INT32 drawHeight = height;
+    double scale = 0.0;
+    ComputeScaledDimensions(extent, width, height, dpi, map->GetMetersPerUnit(), drawWidth, drawHeight, scale);
+
+    //printf("XYZ(%d, %d, %d) -> [%f, %f] [%f, %f] at %f -- (w: %d, h: %d, mpu: %f)\n", x, y, z, mcsMinX, mcsMinY, mcsMaxX, mcsMaxY, scale, width, height, map->GetMetersPerUnit());
+
+    // sanity check - number of image pixels cannot exceed MAX_PIXELS
+    if (drawWidth * drawHeight > MAX_PIXELS)
+        throw new MgOutOfRangeException(L"MgServerRenderingService.RenderMetatileXYZ", __LINE__, __WFILE__, NULL, L"MgInvalidImageSizeTooBig", NULL);
+
+    // initialize the renderer (set clipping to false so that we label
+    // the unclipped geometry)
+    auto_ptr<SE_Renderer> dr(CreateRenderer(m_rendererName, drawWidth, drawHeight, bgColor, false, true, tileExtentOffset));
+
+    // create a temporary collection containing all the layers for the base group
+    Ptr<MgLayerCollection> layers = map->GetLayers();
+    Ptr<MgReadOnlyLayerCollection> roLayers = new MgReadOnlyLayerCollection();
+    for (int i=0; i<layers->GetCount(); i++)
+    {
+        Ptr<MgLayerBase> layer = layers->GetItem(i);
+        Ptr<MgLayerGroup> parentGroup = layer->GetGroup();
+        if (parentGroup == baseGroup)
+            roLayers->Add(layer);
+    }
+
+    // of course the group has to also be visible
+    bool groupVisible = baseGroup->GetVisible();
+    baseGroup->SetVisible(true);
+
+    // call the internal helper API to do all the stylization overhead work
+    STRING format = (metaTilingFactor > 1) ? MgImageFormats::Meta : tileImageFormat;
+    Ptr<MgByteReader> tile;
+    if (metaTilingFactor > 1)
+        tile = RenderMapInternal(map, NULL, roLayers, dr.get(), drawWidth, drawHeight, drawWidth, drawHeight, format, scale, extent, true, true, false, NULL);
+    else
+        tile = RenderMapInternal(map, NULL, roLayers, dr.get(), drawWidth, drawHeight, width, height, format, scale, extent, true, true, false, NULL);
+    ret = new MgMetatile(tile, drawWidth, drawHeight, XYZ_TILE_WIDTH, XYZ_TILE_HEIGHT, tileImageFormat, metaTilingFactor);
+
+    // restore the base group's visibility
+    baseGroup->SetVisible(groupVisible);
+
+    MG_CATCH_AND_THROW(L"MgServerRenderingService.RenderMetatileXYZ")
+
+    return ret.Detach();
+}
+
+MgByteReader* MgServerRenderingService::RenderTileFromMetaTile(MgMap* map,
+    MgMetatile* metaTile,
+    CREFSTRING rendererName,
+    INT32 subTileX,
+    INT32 subTileY)
+{
+    Ptr<MgByteReader> ret;
+
+    MG_TRY()
+
+    CHECKARGUMENTNULL(map, L"MgServerRenderingService.RenderMetatileXYZ");
+
+    Ptr<MgByteReader> image = metaTile->GetImage();
+    INT32 metaTileFactor = metaTile->GetMetaTilingFactor();
+    INT32 origTileWidth = metaTile->GetRequestedWidth();
+    INT32 origTileHeight = metaTile->GetRequestedHeight();
+    STRING origTileFormat = metaTile->GetTileImageFormat();
+
+    MgByteSource* bs = image->GetByteSource();
+    // upcast to bytesource Impl to access bytearray member
+    ByteSourceMemoryImpl* source = dynamic_cast<ByteSourceMemoryImpl*>(bs->GetSourceImpl());
+    _ASSERT(source);
+    _ASSERT(bs->GetMimeType() == MgMimeType::Meta);
+    INT32 size = (INT32)source->GetLength();
+
+    // some checking: the complete meta tile of 32b pixels should be in the framebuffer handed over
+    //INT32 expectedSize = 4 * (metaTileFactor * origTileWidth)
+    //    * (metaTileFactor * origTileHeight);
+    //assert(size == expectedSize);
+    INT32 expectedSize = 4 * metaTile->GetWidth() * metaTile->GetHeight();
+    _ASSERT(size == expectedSize);
+
+    INT32 scaledTileWidth = origTileWidth;
+    INT32 scaledTileHeight = origTileHeight;
+
+    // For extent-driven tile rendering like XYZ, the raw image frame buffer of the tile may not actually 
+    // be square which means the meta-tile itself would not be square, so when we sub-divide we must make 
+    // sure to not sub-divide by the original width/height, but by its *proportially scaled* width/height, 
+    // which we then pass it down to AGGRenderer::Save() to write the buffer back up to the originally 
+    // requested size, scaling as appropriate.
+    if (metaTileFactor > 1)
+    {
+        scaledTileWidth = (INT32)(metaTile->GetWidth() / metaTileFactor);
+        scaledTileHeight = (INT32)(metaTile->GetHeight() / metaTileFactor);
+
+        // This may be the same or may be less, but it can't be more!
+        _ASSERT(scaledTileWidth <= origTileWidth);
+        _ASSERT(scaledTileHeight <= origTileHeight);
+    }
+
+    // use the map's background color, but always make it fully transparent
+    RS_Color bgColor;
+    StylizationUtil::ParseColor(map->GetBackgroundColor(), bgColor);
+    bgColor.alpha() = 0;
+    // Must create renderer under the scaled width/height and not original
+    auto_ptr<SE_Renderer> dr(CreateRenderer(rendererName, scaledTileWidth, scaledTileHeight, bgColor, true));
+    RS_ColorVector tileColorPalette;
+    if (rendererName == L"AGG" && HasColorMap(origTileFormat))
+    {
+        MgMappingUtil::ParseColorStrings(&tileColorPalette, map);
+    }
+
+    // now copy tile from meta framebuffer to new one...
+    // use unsigned int pointer arithmetic for pixels!
+    //MgByte* byteBuffer = source->Bytes();     // here we access the bytearray inside the bytesource
+    MgByte* byteBuffer = source->BytesNoAddRef();
+    assert(size == byteBuffer->GetLength());  // double check the buffer size
+
+    // get pointer to the framebuffer containing the metatile 
+    unsigned int *framebuf = (unsigned int*)byteBuffer->Bytes();
+
+    // allocate subtile buffer to copy new framebuf with changed dimensions
+    // place buffer in allocated MgByte auto_ptr object for destruction at end of scope
+    auto_ptr<MgByte> subTileBytes(new MgByte((unsigned char*)new unsigned int[scaledTileWidth * scaledTileHeight],
+        scaledTileWidth * scaledTileHeight * sizeof(int), MgByte::New));
+    // get the pointer to the internal target buffer
+    unsigned int* subTileBuf = (unsigned int*)subTileBytes->Bytes();
+    for (int y = 0; y < scaledTileHeight; y++)    // rows
+        for (int x = 0; x < scaledTileWidth; x++)    // columns innerloop
+        {  // this copies the 32bit pixels from the meta tile framebuffer to the subtile framebuffer
+            *(subTileBuf + x
+                + y * scaledTileWidth)                 // target address in subtile
+                = *(framebuf + (x + (subTileX * scaledTileWidth))   // X address in meta tile
+                    + (y + (subTileY * scaledTileHeight))  // Y address in meta tile
+                    * scaledTileWidth * metaTileFactor);  // use width of metaTile here
+        }
+    // then call the image renderer to convert the framebuffer bitmap into the desired image format
+    ret = CreateImageFromRenderer(map, dr.get(), origTileWidth, origTileHeight, origTileFormat, rendererName, NULL, subTileBuf);
+
+    MG_CATCH_AND_THROW(L"MgServerRenderingService.RenderTileFromMetaTile")
+
+    return ret.Detach();
+}
+
+bool MgServerRenderingService::HasColorMap(CREFSTRING format)
+{
+    return format == L"PNG8" || format == L"GIF";
+}
+
+MgByteReader* MgServerRenderingService::CreateImageFromRenderer(MgMap* map,
+    Renderer* dr,
+    INT32 saveWidth,
+    INT32 saveHeight,
+    CREFSTRING format,
+    CREFSTRING rendererName,
+    ProfileRenderMapResult* pPRMResult,
+    unsigned int* frameBuffer)
+{
+    if (NULL != pPRMResult)
+    {
         // Set the start time of creating map image
         pPRMResult->SetCreateImageTime(MgTimerUtil::GetTime());
     }
 
-/*
-    //-------------------------------------------------------
-    // draw a border around the tile - used for debugging
-    RS_LineStroke ls;
-    ls.color() = RS_Color(128, 128, 128, 64);
+    /*
+        //-------------------------------------------------------
+        // draw a border around the tile - used for debugging
+        RS_LineStroke ls;
+        ls.color() = RS_Color(128, 128, 128, 64);
 
-    LineBuffer lb(5);
-    double mcsMinX = b.minx;
-    double mcsMaxX = b.maxx;
-    double mcsMinY = b.miny;
-    double mcsMaxY = b.maxy;
-    double incX = (mcsMaxX - mcsMinX) / saveWidth  / 10.0;
-    double incY = (mcsMaxY - mcsMinY) / saveHeight / 10.0;
-    lb.MoveTo(mcsMinX + incX, mcsMinY + incY);
-    lb.LineTo(mcsMaxX - incX, mcsMinY + incY);
-    lb.LineTo(mcsMaxX - incX, mcsMaxY - incY);
-    lb.LineTo(mcsMinX + incX, mcsMaxY - incY);
-    lb.LineTo(mcsMinX + incX, mcsMinY + incY);
-    dr->ProcessPolyline(&lb, ls);
-    //-------------------------------------------------------
-*/
+        LineBuffer lb(5);
+        double mcsMinX = b.minx;
+        double mcsMaxX = b.maxx;
+        double mcsMinY = b.miny;
+        double mcsMaxY = b.maxy;
+        double incX = (mcsMaxX - mcsMinX) / saveWidth  / 10.0;
+        double incY = (mcsMaxY - mcsMinY) / saveHeight / 10.0;
+        lb.MoveTo(mcsMinX + incX, mcsMinY + incY);
+        lb.LineTo(mcsMaxX - incX, mcsMinY + incY);
+        lb.LineTo(mcsMaxX - incX, mcsMaxY - incY);
+        lb.LineTo(mcsMinX + incX, mcsMaxY - incY);
+        lb.LineTo(mcsMinX + incX, mcsMinY + incY);
+        dr->ProcessPolyline(&lb, ls);
+        //-------------------------------------------------------
+    */
 
     // get a byte representation of the image
     auto_ptr<RS_ByteData> data;
@@ -2493,26 +2633,34 @@
     try
     {
         // call the image renderer to create the image
-        if (wcscmp(m_rendererName.c_str(), L"AGG") == 0)
+        if (wcscmp(rendererName.c_str(), L"AGG") == 0)
         {
-            //-------------------------------------------------------
-            /// RFC60 code to correct colormaps by UV
-            //-------------------------------------------------------
-            // We examine the expressions collected from xml definitions of all layers.
-            // The map object has a list from all color entries found in the most recent
-            // layer stylization.
-            // * TODO - currently they are interpreted as ffffffff 32-bit RGBA string values
-            // * adding expresssions and other interpretations should be done in ParseColorStrings
-            // * the color Palette for the renderer is a vector<RS_Color>
-            if (hasColorMap(format))
+            if (format == MgImageFormats::Meta)  // we dont use the colorPalette here for meta tiling
+            {                       // as this call only returns the framebuffer
+                data.reset(((AGGRenderer*)dr)->Save(format, saveWidth, saveHeight, NULL, NULL));
+                // copy the framebuffer into the reader, RenderTileFromMetatile is taking it from there
+            }
+            else
             {
-                RS_ColorVector tileColorPalette;
-                MgMappingUtil::ParseColorStrings(&tileColorPalette, map);
-//              printf("<<<<<<<<<<<<<<<<<<<<< MgServerRenderingService::ColorPalette->size(): %d\n", tileColorPalette.size());
-                data.reset(((AGGRenderer*)dr)->Save(format, saveWidth, saveHeight, &tileColorPalette));
+                //-------------------------------------------------------
+                /// RFC60 code to correct colormaps by UV
+                //-------------------------------------------------------
+                // We examine the expressions collected from xml definitions of all layers.
+                // The map object has a list from all color entries found in the most recent
+                // layer stylization.
+                // * TODO - currently they are interpreted as ffffffff 32-bit RGBA string values
+                // * adding expresssions and other interpretations should be done in ParseColorStrings
+                // * the color Palette for the renderer is a vector<RS_Color>
+                if (HasColorMap(format))
+                {
+                    RS_ColorVector tileColorPalette;
+                    MgMappingUtil::ParseColorStrings(&tileColorPalette, map);
+                    //              printf("<<<<<<<<<<<<<<<<<<<<< MgServerRenderingService::ColorPalette->size(): %d\n", tileColorPalette.size());
+                    data.reset(((AGGRenderer*)dr)->Save(format, saveWidth, saveHeight, &tileColorPalette, frameBuffer));
+                }
+                else
+                    data.reset(((AGGRenderer*)dr)->Save(format, saveWidth, saveHeight, NULL, frameBuffer));
             }
-            else
-                data.reset(((AGGRenderer*)dr)->Save(format, saveWidth, saveHeight, NULL));
         }
         else
             data.reset(((GDRenderer*)dr)->Save(format, saveWidth, saveHeight));
@@ -2536,11 +2684,13 @@
             bs->SetMimeType(MgMimeType::Png);
         else if (format == MgImageFormats::Tiff)
             bs->SetMimeType(MgMimeType::Tiff);
+        else if (format == MgImageFormats::Meta)         // add a mimetype for our metatile
+            bs->SetMimeType(MgMimeType::Meta);
     }
     else
-        throw new MgNullReferenceException(L"MgServerRenderingService.CreateImage", __LINE__, __WFILE__, NULL, L"MgNoDataFromRenderer", NULL);
-    
-    if(NULL != pPRMResult)
+        throw new MgNullReferenceException(L"MgImageProcessor.CreateImageFromRenderer", __LINE__, __WFILE__, NULL, L"MgNoDataFromRenderer", NULL);
+
+    if (NULL != pPRMResult)
     {
         // Calculate the time spent on stylizing labels
         double createImageTime = MgTimerUtil::GetTime() - pPRMResult->GetCreateImageTime();
@@ -2547,8 +2697,8 @@
         pPRMResult->SetCreateImageTime(createImageTime);
 
         pPRMResult->SetImageFormat(format);
-        pPRMResult->SetRendererType(m_rendererName);
+        pPRMResult->SetRendererType(rendererName);
     }
 
     return bs->GetReader();
-}
+}
\ No newline at end of file

Modified: trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.h
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -20,7 +20,10 @@
 
 #include "ServerRenderingDllExport.h"
 
+struct RS_Bounds;
+class RS_Color;
 class SE_Renderer;
+class Renderer;
 class FeatureInfoRenderer;
 class MgFeatureInformation;
 class Stylizer;
@@ -56,6 +59,16 @@
                                      INT32 tileDpi,
                                      CREFSTRING tileImageFormat);
 
+    virtual MgByteReader* RenderTile(MgMap* map,
+                                     CREFSTRING baseMapLayerGroupName,
+                                     INT32 tileColumn,
+                                     INT32 tileRow,
+                                     INT32 tileWidth,
+                                     INT32 tileHeight,
+                                     INT32 tileDpi,
+                                     CREFSTRING tileImageFormat,
+                                     double tileExtentOffset);
+
     virtual MgByteReader* RenderTileXYZ(MgMap* map,
                                         CREFSTRING baseMapLayerGroupName,
                                         INT32 x,
@@ -70,6 +83,15 @@
                                         INT32 dpi,
                                         CREFSTRING tileImageFormat);
 
+    virtual MgByteReader* RenderTileXYZ(MgMap* map,
+                                        CREFSTRING baseMapLayerGroupName,
+                                        INT32 x,
+                                        INT32 y,
+                                        INT32 z,
+                                        INT32 dpi,
+                                        CREFSTRING tileImageFormat,
+                                        double tileExtentOffset);
+
     virtual MgByteReader* RenderTileUTFGrid(MgMap* map,
                                             CREFSTRING baseMapLayerGroupName,
                                             INT32 x,
@@ -243,20 +265,58 @@
                                         bool bIncludeFeatureBBOX,
                                         bool bIncludeGeometry);
 
+    virtual MgMetatile* RenderMetatile(MgMap* map,
+                                       CREFSTRING baseMapLayerGroupName,
+                                       INT32 tileColumn,
+                                       INT32 tileRow,
+                                       INT32 tileWidth,
+                                       INT32 tileHeight,
+                                       INT32 tileDpi,
+                                       CREFSTRING tileImageFormat,
+                                       double tileExtentOffset,
+                                       INT32 metaTilingFactor);
+
+    virtual MgMetatile* RenderMetatileXYZ(MgMap* map,
+                                          CREFSTRING baseMapLayerGroupName,
+                                          INT32 x,
+                                          INT32 y,
+                                          INT32 z,
+                                          INT32 dpi,
+                                          CREFSTRING tileImageFormat,
+                                          double tileExtentOffset,
+                                          INT32 metaTilingFactor);
+
+    virtual MgByteReader* RenderTileFromMetaTile(MgMap* map, MgMetatile* metaTile, CREFSTRING rendererName, INT32 subTileX, INT32 subTileY);
+
 private:
+    static bool HasColorMap(CREFSTRING format);
+
+    static MgByteReader* CreateImageFromRenderer(MgMap* map,
+                                                 Renderer* dr,
+                                                 INT32 saveWidth,
+                                                 INT32 saveHeight,
+                                                 CREFSTRING format,
+                                                 CREFSTRING rendererName,
+                                                 ProfileRenderMapResult* pPRMResult,
+                                                 unsigned int* frameBuffer = NULL);
+
+    static void ComputeScaledDimensions(RS_Bounds& extent, INT32 width, INT32 height, INT32 dpi,
+                                        double metersPerUnit, INT32& drawWidth, INT32& drawHeight, double& scale);
+
     static void ComputeXYZTileExtents(MgMap* map, INT32 x, INT32 y, INT32 z, RS_Bounds& extent);
     // used for tile generation
-    MgByteReader* RenderTile(MgMap* map,
-                             MgLayerGroup* baseGroup,
-                             INT32 scaleIndex,
-                             INT32 width,
-                             INT32 height,
-                             double scale,
-                             double mcsMinX,
-                             double mcsMaxX,
-                             double mcsMinY,
-                             double mcsMaxY,
-                             CREFSTRING format);
+    MgByteReader* RenderTileInternal(MgMap* map,
+                                     MgLayerGroup* baseGroup,
+                                     INT32 scaleIndex,
+                                     INT32 width,
+                                     INT32 height,
+                                     double scale,
+                                     double mcsMinX,
+                                     double mcsMaxX,
+                                     double mcsMinY,
+                                     double mcsMaxY,
+                                     CREFSTRING format,
+                                     double tileExtentOffset);
 
     // helper used by other methods
     MgByteReader* RenderMapInternal(MgMap* map,
@@ -300,12 +360,13 @@
                          INT32 layerAttributeFilter,
                          FeatureInfoRenderer* selRenderer);
 
-    SE_Renderer* CreateRenderer(int width,
-                                int height,
-                                RS_Color& bgColor,
-                                bool requiresClipping,
-                                bool localOverposting = false,
-                                double tileExtentOffset = 0.0);
+    virtual SE_Renderer* CreateRenderer(CREFSTRING rendererName,
+                                        INT32 width,
+                                        INT32 height,
+                                        RS_Color& bgColor,
+                                        bool requiresClipping,
+                                        bool localOverposting = false,
+                                        double tileExtentOffset = 0.0);
 
     void RenderLayers(MgMap* map,
                       MgReadOnlyLayerCollection* layers,
@@ -338,13 +399,6 @@
                           INT32 saveHeight,
                           ProfileRenderMapResult* pPRMResult);
 
-    MgByteReader* CreateImage(MgMap* map,
-                              Renderer* dr,
-                              INT32 saveWidth,
-                              INT32 saveHeight,
-                              CREFSTRING format,
-                              ProfileRenderMapResult* pPRMResult);
-
     // member data
     Ptr<MgFeatureService> m_svcFeature;
     Ptr<MgResourceService> m_svcResource;

Modified: trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.vcxproj
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.vcxproj	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.vcxproj	2019-05-27 12:24:20 UTC (rev 9510)
@@ -228,6 +228,18 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="OpRenderMetatile.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+    </ClCompile>
+    <ClCompile Include="OpRenderMetatileXYZ.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="OpRenderTile.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
@@ -234,6 +246,12 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="OpRenderTileFromMetatile.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="OpRenderTileXYZ.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
@@ -296,7 +314,10 @@
     <ClInclude Include="OpRenderDynamicOverlay.h" />
     <ClInclude Include="OpRenderMap.h" />
     <ClInclude Include="OpRenderMapLegend.h" />
+    <ClInclude Include="OpRenderMetatile.h" />
+    <ClInclude Include="OpRenderMetatileXYZ.h" />
     <ClInclude Include="OpRenderTile.h" />
+    <ClInclude Include="OpRenderTileFromMetatile.h" />
     <ClInclude Include="OpRenderTileXYZ.h" />
     <ClInclude Include="OpRenderTileUTFGrid.h" />
     <ClInclude Include="RenderingOperation.h" />

Modified: trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.vcxproj.filters
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.vcxproj.filters	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.vcxproj.filters	2019-05-27 12:24:20 UTC (rev 9510)
@@ -42,6 +42,15 @@
     <ClCompile Include="OpRenderTileUTFGrid.cpp">
       <Filter>Ops</Filter>
     </ClCompile>
+    <ClCompile Include="OpRenderMetatile.cpp">
+      <Filter>Ops</Filter>
+    </ClCompile>
+    <ClCompile Include="OpRenderMetatileXYZ.cpp">
+      <Filter>Ops</Filter>
+    </ClCompile>
+    <ClCompile Include="OpRenderTileFromMetatile.cpp">
+      <Filter>Ops</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="OpQueryFeatureProperties.h">
@@ -80,6 +89,15 @@
     <ClInclude Include="OpRenderTileUTFGrid.h">
       <Filter>Ops</Filter>
     </ClInclude>
+    <ClInclude Include="OpRenderMetatile.h">
+      <Filter>Ops</Filter>
+    </ClInclude>
+    <ClInclude Include="OpRenderMetatileXYZ.h">
+      <Filter>Ops</Filter>
+    </ClInclude>
+    <ClInclude Include="OpRenderTileFromMetatile.h">
+      <Filter>Ops</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="ServerRenderingService.rc" />

Modified: trunk/MgDev/Server/src/Services/Rendering/ServerRenderingServiceBuild.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/ServerRenderingServiceBuild.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Rendering/ServerRenderingServiceBuild.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -23,6 +23,8 @@
 #include "OpRenderDynamicOverlay.cpp"
 #include "OpRenderMap.cpp"
 #include "OpRenderMapLegend.cpp"
+#include "OpRenderMetatile.cpp"
+#include "OpRenderTileFromMetatile.cpp"
 #include "OpRenderTile.cpp"
 #include "OpRenderTileXYZ.cpp"
 #include "OpRenderTileUTFGrid.cpp"

Copied: trunk/MgDev/Server/src/Services/Tile/MetatileLockUtil.cpp (from rev 9509, sandbox/jng/tiling_v2/Server/src/Services/Tile/MetatileLockUtil.cpp)
===================================================================
--- trunk/MgDev/Server/src/Services/Tile/MetatileLockUtil.cpp	                        (rev 0)
+++ trunk/MgDev/Server/src/Services/Tile/MetatileLockUtil.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -0,0 +1,116 @@
+//
+//  Copyright (C) 2004-2019 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 "MapGuideCommon.h"
+#include "MetatileLockUtil.h"
+
+std::map<STRING, ACE_Condition<ACE_Recursive_Thread_Mutex>*> MgMetatileLockUtil::sm_lockMap;
+ACE_Recursive_Thread_Mutex MgMetatileLockUtil::sm_tileMutex;
+
+MgMetatileLockUtil::MgMetatileLockUtil() { }
+
+MgMetatileLockUtil::~MgMetatileLockUtil() { }
+
+void MgMetatileLockUtil::WaitForLock(CREFSTRING tilePathname)
+{
+    // protect test of lockmap entry with a guard
+    ACE_Condition<ACE_Recursive_Thread_Mutex> *tileLock = 0;
+    // look for lock and wait if found
+    ACE_MT(ACE_GUARD(ACE_Recursive_Thread_Mutex, ace_mon, sm_tileMutex));
+    if (tileLock = sm_lockMap[tilePathname])
+    {   // lock has been found
+#ifdef _DEBUG
+        std::wstringstream text;
+        text << L"(" << ACE_OS::thr_self() << L") METATILE: WaitForLock1("
+            << tilePathname << L")\n";
+        ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+        //                ACE_Time_Value waitTime(METATILEWAIT);
+        int ret = tileLock->wait();    // returns -1 if timed out which is not supposed to happen
+        if (-1 == ret) // so the wait might never come back!!! and the thread gets cleaned out by the service manager
+        {
+#ifdef _DEBUG
+            std::wstringstream text;
+            text << L"(" << ACE_OS::thr_self() << L") METATILE: WaitForLockTimedOut1(" << tilePathname << L")\n";
+            ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+            MgStringCollection arguments;
+            arguments.Add(tilePathname);
+            throw new MgFileIoException(L"MgServerTileService.GetTile",
+                __LINE__, __WFILE__, &arguments, L"MgWaitForLockTimedOut", NULL);
+        }
+#ifdef _DEBUG
+        {
+            std::wstringstream text;
+            text << L"(" << ACE_OS::thr_self() << L") METATILE: LockReleased(" << tilePathname << L")\n";
+            ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+        }
+#endif
+    }
+}
+
+void MgMetatileLockUtil::ClearLock(CREFSTRING tilePathname)
+{
+    try
+    {   // synchronize access to map
+        ACE_MT(ACE_GUARD(ACE_Recursive_Thread_Mutex, ace_mon, sm_tileMutex));
+        ACE_Condition<ACE_Recursive_Thread_Mutex> *lock = sm_lockMap[tilePathname];
+        if (lock)
+        {
+            int result = lock->broadcast();    // notify waiters of finished tile
+#ifdef _DEBUG
+            std::wstringstream text;
+            text << L"(" << ACE_OS::thr_self() << L") METATILE: Broadcast(" << tilePathname
+                << L") returns: " << result << L"\n";
+            ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+            sm_lockMap.erase(tilePathname); // erase only existing lock: this destroys the ace condition
+        }
+        else
+        {
+#ifdef _DEBUG
+            std::wstringstream text;
+            text << L"(" << ACE_OS::thr_self() << L") METATILE: NoLockFor(" << tilePathname << L")\n";
+            ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+        }
+    }
+    catch (MgException* e)
+    {
+#ifdef _DEBUG
+        std::wstringstream text;
+        text << L"(" << ACE_OS::thr_self() << L") METATILE: Broadcastcrash (" << tilePathname
+            << L")\n" << e->GetExceptionMessage() << L")\n";
+        ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+        throw e;
+    }
+}
+
+void MgMetatileLockUtil::LockTile(CREFSTRING tilePathname)
+{
+    // create ace condition for this tile as we are going to make it
+    ACE_Condition<ACE_Recursive_Thread_Mutex> *tileLock = new ACE_Condition<ACE_Recursive_Thread_Mutex>(sm_tileMutex);
+    // keep reference to lock (ace condition) in our map
+    sm_lockMap[tilePathname] = tileLock;
+
+#ifdef _DEBUG
+    std::wstringstream text;
+    text << L"(" << ACE_OS::thr_self() << L") METATILE: CreateLock(" << tilePathname << L"\n";
+    ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+}
\ No newline at end of file

Copied: trunk/MgDev/Server/src/Services/Tile/MetatileLockUtil.h (from rev 9509, sandbox/jng/tiling_v2/Server/src/Services/Tile/MetatileLockUtil.h)
===================================================================
--- trunk/MgDev/Server/src/Services/Tile/MetatileLockUtil.h	                        (rev 0)
+++ trunk/MgDev/Server/src/Services/Tile/MetatileLockUtil.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -0,0 +1,39 @@
+//
+//  Copyright (C) 2004-2019 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
+//
+
+
+#ifndef MG_METATILE_LOCK_UTIL_H
+#define MG_METATILE_LOCK_UTIL_H
+
+/// \brief
+/// Metatile helper class for file-based locking
+class MgMetatileLockUtil
+{
+public:
+    MgMetatileLockUtil();
+    ~MgMetatileLockUtil();
+
+    void WaitForLock(CREFSTRING tilePathname);
+    void ClearLock(CREFSTRING tilePathname);
+    void LockTile(CREFSTRING tilePathname);
+
+private:
+    static std::map<STRING, ACE_Condition<ACE_Recursive_Thread_Mutex>*> sm_lockMap;
+    static ACE_Recursive_Thread_Mutex sm_tileMutex;
+};
+
+#endif
\ No newline at end of file

Modified: trunk/MgDev/Server/src/Services/Tile/ServerTileService.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Tile/ServerTileService.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Tile/ServerTileService.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -26,13 +26,17 @@
 MgServerTileService::MgServerTileService() : MgTileService()
 {
     MgTileCacheDefault::Initialize();
+
+    MgConfiguration* pConf = MgConfiguration::GetInstance();
+    pConf->GetStringValue(MgConfigProperties::GeneralPropertiesSection,
+        MgConfigProperties::GeneralPropertyRenderer,
+        m_rendererName,
+        MgConfigProperties::DefaultGeneralPropertyRenderer);
 }
 
 MgServerTileService::~MgServerTileService()
-{
+{ }
 
-}
-
 MgByteReader* MgServerTileService::GetTile(
     MgMap* map,
     CREFSTRING baseMapLayerGroupName,
@@ -57,8 +61,8 @@
             __LINE__, __WFILE__, NULL, L"", NULL);
     }
 
-    Ptr<MgTileCacheDefault> cache = new MgTileCacheDefault(map);
-    ret = cache->GetTile(baseMapLayerGroupName, tileColumn, tileRow, scaleIndex);
+    MgTileCacheDefault cache(map);
+    ret = cache.GetTile(baseMapLayerGroupName, tileColumn, tileRow, scaleIndex);
 
     MG_CATCH_AND_THROW(L"MgServerTileService.GetTile")
 
@@ -93,8 +97,8 @@
 
     CHECKARGUMENTNULL(map, L"MgServerTileService.ClearCache");
 
-    Ptr<MgTileCacheDefault> cache = new MgTileCacheDefault(map);
-    cache->Clear();
+    MgTileCacheDefault cache(map);
+    cache.Clear();
 
     MG_CATCH_AND_THROW(L"MgServerTileService.ClearCache")
 }
@@ -313,6 +317,22 @@
         xml.append("<DefaultValue></DefaultValue>\n");
         xml.append("</ConnectionProperty>\n");
 
+        //Property: TileExtentOffset
+        xml.append("<ConnectionProperty Enumerable=\"false\" Protected=\"false\" Required=\"true\">\n");
+        xml.append("<Name>");
+        std::string mbTileExtentOffset;
+        MgUtil::WideCharToMultiByte(MG_TILE_PROVIDER_COMMON_PARAM_TILEEXTENTOFFSET, mbTileExtentOffset);
+        xml.append(mbTileExtentOffset);
+        xml.append("</Name>\n");
+        xml.append("<LocalizedName>");
+        std::string mbLocTileExtentOffset;
+        STRING wLocTileExtentOffset = MgUtil::GetResourceMessage(MgResources::TileService, L"MgTileProvider_Common_Property_TileExtentOffset_LocalizedName");
+        MgUtil::WideCharToMultiByte(wLocTileExtentOffset, mbLocTileExtentOffset);
+        xml.append(mbLocTileExtentOffset);
+        xml.append("</LocalizedName>\n");
+        xml.append("<DefaultValue></DefaultValue>\n");
+        xml.append("</ConnectionProperty>\n");
+
         xml.append("</ConnectionProperties>\n");
         xml.append("</TileProvider>\n");
     }
@@ -397,6 +417,23 @@
         xml.append("<DefaultValue>false</DefaultValue>\n");
         xml.append("</ConnectionProperty>\n");
 
+        //Property: TileExtentOffset
+        xml.append("<ConnectionProperty Enumerable=\"false\" Protected=\"false\" Required=\"true\">\n");
+        xml.append("<Name>");
+        std::string mbTileExtentOffset;
+        MgUtil::WideCharToMultiByte(MG_TILE_PROVIDER_COMMON_PARAM_TILEEXTENTOFFSET, mbTileExtentOffset);
+        xml.append(mbTileExtentOffset);
+        xml.append("</Name>\n");
+        xml.append("<LocalizedName>");
+        std::string mbLocTileExtentOffset;
+        STRING wLocTileExtentOffset = MgUtil::GetResourceMessage(MgResources::TileService, L"MgTileProvider_Common_Property_TileExtentOffset_LocalizedName");
+        MgUtil::WideCharToMultiByte(wLocTileExtentOffset, mbLocTileExtentOffset);
+        xml.append(mbLocTileExtentOffset);
+        xml.append("</LocalizedName>\n");
+        xml.append("<DefaultValue></DefaultValue>\n");
+        xml.append("</ConnectionProperty>\n");
+
+
         xml.append("</ConnectionProperties>\n");
         xml.append("</TileProvider>\n");
     }
@@ -503,9 +540,12 @@
         MdfModel::NameStringPairCollection* parameters = tilesetParams->GetParameters();
         INT32 width = 300;
         INT32 height = 300;
+        double tileExtentOffset = MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset;
         STRING format = L"PNG";
         STRING path;
         bool bRenderOnly = false;
+        INT32 metaTileFactor = 0;
+        INT32 metaTileLockMethod = 0;
         for (INT32 i = 0; i < parameters->GetCount(); i++)
         {
             MdfModel::NameStringPair* pair = parameters->GetAt(i);
@@ -529,6 +569,22 @@
             {
                 bRenderOnly = MgUtil::StringToBoolean(pair->GetValue());
             }
+            else if (pair->GetName() == MG_TILE_PROVIDER_COMMON_PARAM_TILEEXTENTOFFSET)
+            {
+                tileExtentOffset = MgUtil::StringToDouble(pair->GetValue());
+                if (tileExtentOffset < 0.0)
+                {
+                    tileExtentOffset = MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset;
+                }
+            }
+            else if (pair->GetName() == MG_TILE_PROVIDER_COMMON_PARAM_METATILEFACTOR)
+            {
+                metaTileFactor = MgUtil::StringToInt32(pair->GetValue());
+            }
+            else if (pair->GetName() == MG_TILE_PROVIDER_COMMON_PARAM_METATILELOCKMETHOD)
+            {
+                metaTileLockMethod = MgUtil::StringToInt32(pair->GetValue());
+            }
         }
 
         //If we find the cache path substitution tag, replace it with the default path from the configuration
@@ -537,14 +593,17 @@
             path = MgTileParameters::tileCachePath;
         }
 
-        cache = new MgTileCacheDefaultProvider(tileSetId, path, width, height, format, bRenderOnly);
+        cache = new MgTileCacheDefaultProvider(tileSetId, path, width, height, format, bRenderOnly, tileExtentOffset, m_rendererName, metaTileFactor, metaTileLockMethod);
     }
     else if (provider == MG_TILE_PROVIDER_XYZ)
     {
         MdfModel::NameStringPairCollection* parameters = tilesetParams->GetParameters();
         STRING format = L"PNG";
+        double tileExtentOffset = MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset;
         STRING path;
         bool bRenderOnly = false;
+        INT32 metaTileFactor = 0;
+        INT32 metaTileLockMethod = 0;
         for (INT32 i = 0; i < parameters->GetCount(); i++)
         {
             MdfModel::NameStringPair* pair = parameters->GetAt(i);
@@ -560,6 +619,22 @@
             {
                 bRenderOnly = MgUtil::StringToBoolean(pair->GetValue());
             }
+            else if (pair->GetName() == MG_TILE_PROVIDER_COMMON_PARAM_TILEEXTENTOFFSET)
+            {
+                tileExtentOffset = MgUtil::StringToDouble(pair->GetValue());
+                if (tileExtentOffset < 0.0)
+                {
+                    tileExtentOffset = MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset;
+                }
+            }
+            else if (pair->GetName() == MG_TILE_PROVIDER_COMMON_PARAM_METATILEFACTOR)
+            {
+                metaTileFactor = MgUtil::StringToInt32(pair->GetValue());
+            }
+            else if (pair->GetName() == MG_TILE_PROVIDER_COMMON_PARAM_METATILELOCKMETHOD)
+            {
+                metaTileLockMethod = MgUtil::StringToInt32(pair->GetValue());
+            }
         }
 
         //If we find the cache path substitution tag, replace it with the default path from the configuration
@@ -568,7 +643,7 @@
             path = MgTileParameters::tileCachePath;
         }
 
-        cache = new MgTileCacheXYZProvider(tileSetId, path, format, bRenderOnly);
+        cache = new MgTileCacheXYZProvider(tileSetId, path, format, bRenderOnly, tileExtentOffset, m_rendererName, metaTileFactor, metaTileLockMethod);
     }
     else 
     {

Modified: trunk/MgDev/Server/src/Services/Tile/ServerTileService.h
===================================================================
--- trunk/MgDev/Server/src/Services/Tile/ServerTileService.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Tile/ServerTileService.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -78,6 +78,8 @@
 private:
     MgTileCache* GetTileCache(MgResourceIdentifier* resource);
     MgTileCache* GetTileCache(MgResourceIdentifier* tileSetId, MdfModel::TileSetDefinition* tileset);
+
+    STRING m_rendererName;
 };
 
 #endif

Modified: trunk/MgDev/Server/src/Services/Tile/ServerTileService.vcxproj
===================================================================
--- trunk/MgDev/Server/src/Services/Tile/ServerTileService.vcxproj	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Tile/ServerTileService.vcxproj	2019-05-27 12:24:20 UTC (rev 9510)
@@ -94,7 +94,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
-      <AdditionalIncludeDirectories>..\..\..\..\Common\Foundation;..\..\..\..\Common\Geometry;..\..\..\..\Common\PlatformBase;..\..\..\..\Common\MapGuideCommon;..\..\..\..\Common\MdfModel;..\..\..\..\Common\MdfParser;..\..\Common;..\..\Common\Base;..\..\Common\Manager;..\Mapping;..\..\..\..\Oem\ACE\ACE_wrappers;..\..\..\..\Oem\FDO\inc;..\..\..\..\Oem\dbxml\xerces-c-src\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\..\..\..\Common\Foundation;..\..\..\..\Common\Geometry;..\..\..\..\Common\PlatformBase;..\..\..\..\Common\MapGuideCommon;..\..\..\..\Common\MdfModel;..\..\..\..\Common\MdfParser;..\..\Common;..\..\Common\Base;..\..\Common\Manager;..\Rendering;..\Mapping;..\..\..\..\Oem\ACE\ACE_wrappers;..\..\..\..\Oem\FDO\inc;..\..\..\..\Oem\dbxml\xerces-c-src\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;MG_SERVER_TILE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ExceptionHandling>Async</ExceptionHandling>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
@@ -120,7 +120,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
-      <AdditionalIncludeDirectories>..\..\..\..\Common\Foundation;..\..\..\..\Common\Geometry;..\..\..\..\Common\PlatformBase;..\..\..\..\Common\MapGuideCommon;..\..\..\..\Common\MdfModel;..\..\..\..\Common\MdfParser;..\..\Common;..\..\Common\Base;..\..\Common\Manager;..\Mapping;..\..\..\..\Oem\ACE\ACE_wrappers;..\..\..\..\Oem\FDO\inc;..\..\..\..\Oem\dbxml\xerces-c-src\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\..\..\..\Common\Foundation;..\..\..\..\Common\Geometry;..\..\..\..\Common\PlatformBase;..\..\..\..\Common\MapGuideCommon;..\..\..\..\Common\MdfModel;..\..\..\..\Common\MdfParser;..\..\Common;..\..\Common\Base;..\..\Common\Manager;..\Rendering;..\Mapping;..\..\..\..\Oem\ACE\ACE_wrappers;..\..\..\..\Oem\FDO\inc;..\..\..\..\Oem\dbxml\xerces-c-src\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;MG_SERVER_TILE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ExceptionHandling>Async</ExceptionHandling>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
@@ -146,7 +146,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <ClCompile>
       <Optimization>MaxSpeed</Optimization>
-      <AdditionalIncludeDirectories>..\..\..\..\Common\Foundation;..\..\..\..\Common\Geometry;..\..\..\..\Common\PlatformBase;..\..\..\..\Common\MapGuideCommon;..\..\..\..\Common\MdfModel;..\..\..\..\Common\MdfParser;..\..\Common;..\..\Common\Base;..\..\Common\Manager;..\Mapping;..\..\..\..\Oem\ACE\ACE_wrappers;..\..\..\..\Oem\FDO\inc;..\..\..\..\Oem\dbxml\xerces-c-src\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\..\..\..\Common\Foundation;..\..\..\..\Common\Geometry;..\..\..\..\Common\PlatformBase;..\..\..\..\Common\MapGuideCommon;..\..\..\..\Common\MdfModel;..\..\..\..\Common\MdfParser;..\..\Common;..\..\Common\Base;..\..\Common\Manager;..\Rendering;..\Mapping;..\..\..\..\Oem\ACE\ACE_wrappers;..\..\..\..\Oem\FDO\inc;..\..\..\..\Oem\dbxml\xerces-c-src\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;MG_SERVER_TILE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ExceptionHandling>Async</ExceptionHandling>
       <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
@@ -173,7 +173,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <ClCompile>
       <Optimization>MaxSpeed</Optimization>
-      <AdditionalIncludeDirectories>..\..\..\..\Common\Foundation;..\..\..\..\Common\Geometry;..\..\..\..\Common\PlatformBase;..\..\..\..\Common\MapGuideCommon;..\..\..\..\Common\MdfModel;..\..\..\..\Common\MdfParser;..\..\Common;..\..\Common\Base;..\..\Common\Manager;..\Mapping;..\..\..\..\Oem\ACE\ACE_wrappers;..\..\..\..\Oem\FDO\inc;..\..\..\..\Oem\dbxml\xerces-c-src\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\..\..\..\Common\Foundation;..\..\..\..\Common\Geometry;..\..\..\..\Common\PlatformBase;..\..\..\..\Common\MapGuideCommon;..\..\..\..\Common\MdfModel;..\..\..\..\Common\MdfParser;..\..\Common;..\..\Common\Base;..\..\Common\Manager;..\Rendering;..\Mapping;..\..\..\..\Oem\ACE\ACE_wrappers;..\..\..\..\Oem\FDO\inc;..\..\..\..\Oem\dbxml\xerces-c-src\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;MG_SERVER_TILE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ExceptionHandling>Async</ExceptionHandling>
       <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
@@ -198,6 +198,12 @@
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
+    <ClCompile Include="MetatileLockUtil.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="OpClearCache.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
@@ -291,6 +297,7 @@
     </ClCompile>
   </ItemGroup>
   <ItemGroup>
+    <ClInclude Include="MetatileLockUtil.h" />
     <ClInclude Include="OpClearCache.h" />
     <ClInclude Include="OpGetDefaultTileSizeX.h" />
     <ClInclude Include="OpGetDefaultTileSizeY.h" />
@@ -336,6 +343,10 @@
       <Project>{adbf25e2-c629-4832-b315-f12abde05632}</Project>
       <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
     </ProjectReference>
+    <ProjectReference Include="..\Rendering\ServerRenderingService.vcxproj">
+      <Project>{561F38EE-E22F-481A-8EFB-DC0AA25B4C20}</Project>
+      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+    </ProjectReference>
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">

Modified: trunk/MgDev/Server/src/Services/Tile/ServerTileService.vcxproj.filters
===================================================================
--- trunk/MgDev/Server/src/Services/Tile/ServerTileService.vcxproj.filters	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Tile/ServerTileService.vcxproj.filters	2019-05-27 12:24:20 UTC (rev 9510)
@@ -38,6 +38,7 @@
       <Filter>Ops</Filter>
     </ClCompile>
     <ClCompile Include="TileCacheXYZProvider.cpp" />
+    <ClCompile Include="MetatileLockUtil.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="OpClearCache.h">
@@ -72,6 +73,7 @@
       <Filter>Ops</Filter>
     </ClInclude>
     <ClInclude Include="TileCacheXYZProvider.h" />
+    <ClInclude Include="MetatileLockUtil.h" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="ServerTileService.rc" />

Modified: trunk/MgDev/Server/src/Services/Tile/ServerTileServiceBuild.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Tile/ServerTileServiceBuild.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Tile/ServerTileServiceBuild.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -18,6 +18,7 @@
 #ifdef _WIN32
 #include "Dll.cpp"
 #endif
+#include "MetatileLockUtil.cpp"
 #include "OpClearCache.cpp"
 #include "OpGetTile.cpp"
 #include "OpSetTile.cpp"

Modified: trunk/MgDev/Server/src/Services/Tile/TileCacheDefault.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Tile/TileCacheDefault.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Tile/TileCacheDefault.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -218,8 +218,6 @@
     while (NULL == ret)
     {
         // Attempt use a cached & serialized MgMap object
-        Ptr<MgMemoryStreamHelper> cachedMap;
-        STRING mapString = resource->ToString();
         Ptr<MgMap> map;
 
         // Protect the serialized MgMap cache with a mutex.  Stream reading is not
@@ -264,43 +262,9 @@
             {
                 ACE_OS::fclose(lockFile);
             }
-
-            MapCache::const_iterator iter = sm_mapCache.find(mapString);
-            if (sm_mapCache.end() != iter)
-            {
-                cachedMap = SAFE_ADDREF((*iter).second);
-                cachedMap->Rewind();
-                Ptr<MgStream> stream = new MgStream(cachedMap);
-                map = new MgMap();
-                map->Deserialize(stream);
-            }
-            else
-            {
-                Ptr<MgSiteConnection> siteConn = new MgSiteConnection();
-                Ptr<MgUserInformation> userInfo = MgUserInformation::GetCurrentUserInfo();
-                siteConn->Open(userInfo);
-                map = new MgMap(siteConn);
-                map->Create(resourceService, resource, mapString, false /* Allow MgMap to be created from any tile provider if resource is tile set */);
-                cachedMap = new MgMemoryStreamHelper();
-                Ptr<MgStream> stream = new MgStream(cachedMap);
-                map->Serialize(stream);
-                if ((INT32)sm_mapCache.size() >= sm_mapCacheSize)
-                {
-                    ClearMapCache(L"");
-                }
-                sm_mapCache[mapString] = SAFE_ADDREF((MgMemoryStreamHelper*)cachedMap);
-            }
+            GetMapFromDefinition(resource, scaleIndex, map, false /* Allow MgMap to be created from any tile provider if resource is tile set */);
         }   // end of mutex scope
 
-        //Some tile providers (eg. XYZ) may not work with pre-defined scale lists, so the scale index doesn't
-        //resolve to some position in a finite scale list (that won't exist for XYZ), but rather it is part of 
-        //some formula used to determine the appropriate scale
-        if (map->GetFiniteDisplayScaleCount() > 0)
-        {
-            double scale = map->GetFiniteDisplayScaleAt(scaleIndex);
-            map->SetViewScale(scale);
-        }
-
         // Render the tile and cache it.
         ret = RenderAndCacheTile(tilePathname, map, scaleIndex, baseMapLayerGroupName, tileColumn, tileRow);
         break;
@@ -931,3 +895,50 @@
 
     return sm_mapCache.empty();
 }
+
+///////////////////////////////////////////////////////////////////////////////
+/// helper to retrieve map
+///////////////////////////////////////////////////////////////////////////////
+void MgTileCacheDefault::GetMapFromDefinition(MgResourceIdentifier* mapDefinition, INT32 scaleIndex, Ptr<MgMap> &map, bool bStrictCreate)
+{
+    // Attempt use a cached & serialized MgMap object
+    Ptr<MgMemoryStreamHelper> cachedMap;
+    STRING mapString = mapDefinition->ToString();
+
+    MapCache::const_iterator iter = sm_mapCache.find(mapString);
+    if (sm_mapCache.end() != iter)
+    {
+        cachedMap = SAFE_ADDREF((*iter).second);
+        cachedMap->Rewind();
+        Ptr<MgStream> stream = new MgStream(cachedMap);
+        map = new MgMap();
+        map->Deserialize(stream);
+    }
+    else
+    {
+        // get the service from our helper method
+        Ptr<MgResourceService> resourceService = GetResourceServiceForMapDef(mapDefinition, L"MgTileCacheDefault.GetMapFromDefinition");
+        Ptr<MgSiteConnection> siteConn = new MgSiteConnection();
+        siteConn->Open(MgUserInformation::GetCurrentUserInfo());
+        map = new MgMap(siteConn);
+        map->Create(resourceService, mapDefinition, mapString, bStrictCreate);
+        cachedMap = new MgMemoryStreamHelper();
+        Ptr<MgStream> stream = new MgStream(cachedMap);
+        map->Serialize(stream);
+        if ((INT32)sm_mapCache.size() >= sm_mapCacheSize)
+        {
+            ClearMapCache(L"");
+        }
+        sm_mapCache[mapString] = SAFE_ADDREF((MgMemoryStreamHelper*)cachedMap);
+    }
+
+    //Some tile providers (eg. XYZ) may not work with pre-defined scale lists, so the scale index doesn't
+    //resolve to some position in a finite scale list (that won't exist for XYZ), but rather it is part of 
+    //some formula used to determine the appropriate scale
+    INT32 scaleCount = map->GetFiniteDisplayScaleCount();
+    if (scaleCount > 0 && scaleIndex > 0 && scaleIndex < scaleCount)
+    {
+        double scale = map->GetFiniteDisplayScaleAt(scaleIndex);
+        map->SetViewScale(scale);
+    }
+}
\ No newline at end of file

Modified: trunk/MgDev/Server/src/Services/Tile/TileCacheDefault.h
===================================================================
--- trunk/MgDev/Server/src/Services/Tile/TileCacheDefault.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Tile/TileCacheDefault.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -79,6 +79,16 @@
     void Set(MgByteReader* img, CREFSTRING path);
     MgByteReader* Get(CREFSTRING path);
 
+    void GetMapFromDefinition(MgResourceIdentifier* mapDefinition, INT32 scaleIndex, Ptr<MgMap> &map, bool bStrictCreate = true);
+
+    bool DetectTileLockFile(CREFSTRING lockPathname);
+
+    void GeneratePathNames(int scaleIndex,
+        CREFSTRING group, int tileColumn, int tileRow,
+        STRING& tilePathname, STRING& lockPathname, bool createFullPath);
+
+    STRING CreateFullPath(int scaleIndex, CREFSTRING group, int tileColumn, int tileRow);
+
 private:
     MgByteReader* GetTileForMap(CREFSTRING baseMapLayerGroupName,
                                 INT32 tileColumn,
@@ -85,14 +95,7 @@
                                 INT32 tileRow,
                                 INT32 scaleIndex);
 
-    void GeneratePathNames(int scaleIndex,
-        CREFSTRING group, int tileColumn, int tileRow,
-        STRING& tilePathname, STRING& lockPathname, bool createFullPath);
-
     STRING GetFullPath(int scaleIndex, CREFSTRING group, int tileColumn, int tileRow);
-    
-    STRING CreateFullPath(int scaleIndex, CREFSTRING group, int tileColumn, int tileRow);
-
     STRING GetScaleIndexFolder(int scaleIndex);
     STRING GetRowFolder(int tileRow);
     STRING GetColumnFolder(int tileColumn);
@@ -101,7 +104,6 @@
     STRING GetTileIndexString(int tileIndex, int tilesPerFolder);
 
     MgResourceService* GetResourceServiceForMapDef(MgResourceIdentifier* mapDefinition, CREFSTRING funcName);
-    bool DetectTileLockFile(CREFSTRING lockPathname);
 
     Ptr<MgResourceIdentifier> m_resourceId;
     Ptr<MgMap> m_map;

Modified: trunk/MgDev/Server/src/Services/Tile/TileCacheDefaultProvider.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Tile/TileCacheDefaultProvider.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Tile/TileCacheDefaultProvider.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -17,8 +17,20 @@
 
 #include "MapGuideCommon.h"
 #include "TileCacheDefaultProvider.h"
+#include "MetatileLockUtil.h"
 
-MgTileCacheDefaultProvider::MgTileCacheDefaultProvider(MgResourceIdentifier* tileSetId, CREFSTRING path, INT32 tileWidth, INT32 tileHeight, CREFSTRING format, bool bRenderOnly)
+ACE_Recursive_Thread_Mutex MgTileCacheDefaultProvider::sm_MetaTileMutex;
+
+MgTileCacheDefaultProvider::MgTileCacheDefaultProvider(MgResourceIdentifier* tileSetId,
+    CREFSTRING path,
+    INT32 tileWidth,
+    INT32 tileHeight,
+    CREFSTRING format,
+    bool bRenderOnly,
+    double tileExtentOffset,
+    CREFSTRING rendererName,
+    INT32 metaTileFactor,
+    INT32 metaTileLockMethod)
 {
     m_tilesetId = SAFE_ADDREF(tileSetId);
     m_path = path;
@@ -26,6 +38,10 @@
     m_tileHeight = tileHeight;
     m_format = format;
     m_renderOnly = bRenderOnly;
+    m_tileExtentOffset = tileExtentOffset;
+    m_rendererName = rendererName;
+    m_metaTileFactor = metaTileFactor;
+    m_metaTileLockMethod = metaTileLockMethod;
 }
 
 MgTileCacheDefaultProvider::~MgTileCacheDefaultProvider()
@@ -34,17 +50,20 @@
 }
 
 MgByteReader* MgTileCacheDefaultProvider::GetTile(CREFSTRING baseMapLayerGroupName,
-                                                  INT32 tileColumn,
-                                                  INT32 tileRow,
-                                                  INT32 scaleIndex)
+    INT32 tileColumn,
+    INT32 tileRow,
+    INT32 scaleIndex)
 {
     Ptr<MgByteReader> ret;
     MG_TRY()
 
-    ret = GetTileForResource(m_tilesetId, baseMapLayerGroupName, tileColumn, tileRow, scaleIndex);
+        if (m_metaTileFactor > 1)
+            ret = GetMetatileForResource(m_tilesetId, baseMapLayerGroupName, tileColumn, tileRow, scaleIndex);
+        else
+            ret = GetTileForResource(m_tilesetId, baseMapLayerGroupName, tileColumn, tileRow, scaleIndex);
 
     MG_CATCH_AND_THROW(L"MgTileCacheDefaultProvider.GetTile")
-    return ret.Detach();
+        return ret.Detach();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -56,15 +75,15 @@
 
     // get a rendering service instance
     MgServiceManager* serviceMan = MgServiceManager::GetInstance();
-    assert(NULL != serviceMan);
+    _ASSERT(NULL != serviceMan);
     Ptr<MgRenderingService> svcRendering = dynamic_cast<MgRenderingService*>(
         serviceMan->RequestService(MgServiceType::RenderingService));
-    assert(NULL != svcRendering);
+    _ASSERT(NULL != svcRendering);
 
     if (svcRendering != NULL)
     {
         // generate the tile
-        img = svcRendering->RenderTile(map, baseMapLayerGroupName, tileColumn, tileRow, GetDefaultTileSizeX(), GetDefaultTileSizeY(), map->GetDisplayDpi(), GetTileFormat());
+        img = svcRendering->RenderTile(map, baseMapLayerGroupName, tileColumn, tileRow, GetDefaultTileSizeX(), GetDefaultTileSizeY(), map->GetDisplayDpi(), GetTileFormat(), m_tileExtentOffset);
 
         // cache the tile
         if (!m_renderOnly)
@@ -100,4 +119,297 @@
 STRING MgTileCacheDefaultProvider::GetBasePath()
 {
     return GetBasePathFromResourceId(m_tilesetId, m_path);
+}
+
+MgByteReader* MgTileCacheDefaultProvider::GetMetatileForResource(MgResourceIdentifier* resource,
+    CREFSTRING baseMapLayerGroupName,
+    INT32 tileColumn,
+    INT32 tileRow,
+    INT32 scaleIndex)
+{
+    // Must have a renderer name set
+    _ASSERT(!m_rendererName.empty());
+
+    Ptr<MgByteReader> returnedSubtile;
+    FILE* lockFile = NULL;
+    STRING tilePathname, lockPathname;
+    // metatiling vars
+    FILE* subTileLockFile[MG_METATILE_MAX_FACTOR][MG_METATILE_MAX_FACTOR];
+    STRING subTileLockPathname[MG_METATILE_MAX_FACTOR][MG_METATILE_MAX_FACTOR], subTilePathname[MG_METATILE_MAX_FACTOR][MG_METATILE_MAX_FACTOR];
+    STRING metaTileLockPathname, metaTilePathname;
+    int maxX = -1, maxY = -1, subTileX = -1, subTileY = -1, metaTileColumn = -1, metaTileRow = -1;
+    bool cacheHit = false;
+    MgMetatileLockUtil metaLock;
+
+    MG_TRY()
+
+        // Generate tile and lock pathnames for later use
+        //m_tileCache->GeneratePathnames(mapDefinition, scaleIndex, baseMapLayerGroupName,
+        //    tileColumn, tileRow, tilePathname, lockPathname, false);
+        GeneratePathNames(scaleIndex, baseMapLayerGroupName, tileColumn, tileRow, tilePathname, lockPathname, false);
+
+    subTileX = subTileY = 0;
+    metaTileColumn = tileColumn;
+    metaTileRow = tileRow;
+
+    /*
+    ////// compute additional names and control variables to deal with meta tiles:
+    // a meta tile is m_metaTileFactor^2 larger than the requested subtile so there are less tiles computed per map
+    // the metatile coords are computed from original coords modulo metatilesize
+    // e.g. with metatile=2 4 tiles (0,0)(0,1)(1,0)(1,1) get mapped to metatile(0,0)
+    subTileX = abs(tileColumn % m_metaTileFactor);
+    subTileY = abs(tileRow % m_metaTileFactor);
+    // determine left top corner of metaTile having m_metaTileFactor**2 subtiles
+    metaTileColumn = tileColumn - subTileX;
+    metaTileRow = tileRow - subTileY;
+    */
+    maxX = maxY = m_metaTileFactor;  // init control vars
+    //// iterate subtiles and create all pathnames of the created tiles
+    for (int y = 0; y < maxY; y++)    // subtile rows
+    {
+        for (int x = 0; x < maxX; x++)    // subtile columns
+        {   // Generate tile and lock pathnames (locknames are not used)
+            subTileLockFile[x][y] = NULL;
+            //m_tileCache->GeneratePathnames(mapDefinition, scaleIndex, baseMapLayerGroupName,
+            //    metaTileColumn + x, metaTileRow + y, subTilePathname[x][y], subTileLockPathname[x][y], false);
+
+            GeneratePathNames(scaleIndex, baseMapLayerGroupName,
+                metaTileColumn + x, metaTileRow + y, subTilePathname[x][y], subTileLockPathname[x][y], false);
+        }
+    }
+
+    // Generate tile and lock pathnames for meta tile in the larger grid mod xx 
+    //m_tileCache->GeneratePathnames(mapDefinition, scaleIndex, baseMapLayerGroupName,
+    //    metaTileColumn, metaTileRow, metaTilePathname, metaTileLockPathname, false);
+    GeneratePathNames(scaleIndex, baseMapLayerGroupName,
+        metaTileColumn, metaTileRow, metaTilePathname, metaTileLockPathname, false);
+
+    // m_metaTileLockMethod == 0 is using 1 remapped metalockfile for all subtiles
+    // m_metaTileLockMethod > 0 is using 1 an ace condition mapped by metatilename
+    if (m_metaTileLockMethod)
+    {
+#ifdef _DEBUG
+        {
+            std::wstringstream text;
+            text << L"(" << ACE_OS::thr_self() << L") METATILE: " << "WaitForLock("
+                << tileRow << L", " << tileColumn << L") (" << metaTilePathname << L")\n";
+            ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+        }
+#endif
+        metaLock.WaitForLock(metaTilePathname);
+    }
+    else // this is the same locking strategie as with single tiles but using one mapped metatile locking all subtile requests
+    {
+        bool fileExist = DetectTileLockFile(metaTileLockPathname);
+        if (fileExist)
+        {   // TODO: Handle the exception by displaying a tile with an error message?
+            MgFileUtil::DeleteFile(metaTileLockPathname, true);
+#ifdef _DEBUG
+            std::wstringstream text;
+            text << L"(" << ACE_OS::thr_self() << L") METATILE: " << (fileExist ? L"DeletedDanglingLockfile(" : L"NoLockfile(")
+                << metaTileRow << L", " << metaTileColumn << L") " << metaTileLockPathname << L")\n";
+            ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+        }
+    }
+    // try getting the tile from the cache first time
+    cacheHit = returnedSubtile = Get(tilePathname);
+#ifdef _DEBUG
+    std::wstringstream text;
+    text << L"(" << ACE_OS::thr_self() << (cacheHit ? L") CACHEHIT" : L") CACHEMISS") << L": GetTile("
+        << tileRow << L", " << tileColumn << L") " << tilePathname << L"\n";
+    ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+    // if the reader is NULL then the tile wasn't in the cache and we need to generate it
+    while (NULL == returnedSubtile) // while for if to be able to use break to skip to end;
+    {
+        Ptr<MgMap> map;
+
+        // Protect the serialized MgMap cache with a mutex.  Stream reading is not
+        // thread safe so we need to deserialize the map within the mutex to ensure
+        // that a Rewind() is not called in the middle of a Deserialize().
+        // Lockfile test and creation is in same protected scope.
+        { // ------------------------ Locking Context
+            // Attempt to get the mapcache mutex .
+            ACE_MT(ACE_GUARD_RETURN(ACE_Recursive_Thread_Mutex, ace_mon, sm_MetaTileMutex, NULL));
+            if (m_metaTileLockMethod)
+            {
+                // we DONT need the second lock test here in a signal/wait algorithm
+    //          m_tileCache->WaitForLock(metaTilePathname);
+            }
+            else
+            {
+                if (DetectTileLockFile(metaTileLockPathname)) //
+                {
+                    MgStringCollection arguments;
+                    arguments.Add(metaTileLockPathname);
+                    throw new MgFileIoException(L"MgServerTileService.GetTile",
+                        __LINE__, __WFILE__, &arguments, L"MgUnableToLockMetaTileFile", NULL);
+                }
+            }
+
+            // try getting the tile from the cache second time ???????????????????????????????????????????
+            cacheHit = returnedSubtile = Get(tilePathname);
+
+            if (NULL != returnedSubtile)
+            {
+#ifdef _DEBUG
+                std::wstringstream text;
+                text << L"(" << ACE_OS::thr_self() << L") CACHEHIT2: GetTile(" << tileRow << L", " << tileColumn << L") " << tilePathname << L"\n";
+                ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif        
+                break;  /////////////////////////////////// tile was in tileCache .. done.
+            }
+            else {
+#ifdef _DEBUG
+                std::wstringstream text;
+                text << L"(" << ACE_OS::thr_self() << L") CACHEMISS2: GetTile(" << tileRow << L", " << tileColumn << L") " << tilePathname << L"\n";
+                ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+            }
+            // Create the directory structure all subtiles!
+            for (int y = 0; y < maxY; y++)    // rows
+            {
+                for (int x = 0; x < maxX; x++)    // columns
+                {
+                    //m_tileCache->CreateFullPath(mapDefinition, scaleIndex, baseMapLayerGroupName, 
+                    //    metaTileColumn + x, metaTileRow + y);
+                    CreateFullPath(scaleIndex, baseMapLayerGroupName, metaTileColumn + x, metaTileRow + y);
+                }
+            }
+            // ==================================================  LOCKING
+            if (m_metaTileLockMethod)
+            {
+                metaLock.LockTile(metaTilePathname);    // ace lock once per metatile
+            }
+            else
+            {
+                // Create the lock file 
+                lockFile = ACE_OS::fopen(MG_WCHAR_TO_TCHAR(metaTileLockPathname), ACE_TEXT("wb"));
+
+                if (NULL == lockFile)
+                {
+                    MgStringCollection arguments;
+                    arguments.Add(metaTileLockPathname);
+                    throw new MgFileIoException(L"MgServerTileService.GetTile",
+                        __LINE__, __WFILE__, &arguments, L"MgUnableToCreateLockFile", NULL);
+                }
+                else
+                {
+                    ACE_OS::fclose(lockFile); // and close it right away.
+#ifdef _DEBUG
+                    std::wstringstream text;
+                    text << L"(" << ACE_OS::thr_self() << L") METATILE: CreatedLockfile(" << metaTileRow << L", " << metaTileColumn << L") "
+                        << metaTileLockPathname << L")\n";
+                    ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+                }
+            }
+            //=========================================================================
+            GetMapFromDefinition(resource, scaleIndex, map, false);
+        }   // end of mutex scope
+
+        // Render the larger meta tile but do not cache it yet! (m_metaTileFactor [prohibits caching in GetTile)
+        STRING metaTileName = L"META" + metaTilePathname;
+#ifdef _DEBUG
+        std::wstringstream text;
+        text << L"(" << ACE_OS::thr_self() << L") METATILE: RenderMetaTile(" << metaTileRow << L", " << metaTileColumn << L") " << metaTileName << " using map 0x" << map << L"\n";
+        ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+        MgServiceManager* serviceMan = MgServiceManager::GetInstance();
+        _ASSERT(NULL != serviceMan);
+        Ptr<MgRenderingService> svcRendering = dynamic_cast<MgRenderingService*>(
+            serviceMan->RequestService(MgServiceType::RenderingService));
+
+        // Bail if no server-side impl exists (we can't use proxy impl)
+        if (NULL == svcRendering)
+        {
+            throw new MgServiceNotAvailableException(L"MgTileCacheDefaultProvider.GetMetatileForResource", __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+
+        //Ptr<MgByteReader> metaTileBitMap = GetTile(metaTileName, map, scaleIndex, baseMapLayerGroupName, metaTileColumn, metaTileRow);
+        INT32 tileDpi = map->GetDisplayDpi();
+        Ptr<MgMetatile> metaTile = svcRendering->RenderMetatile(map, baseMapLayerGroupName, metaTileColumn, metaTileRow, m_tileWidth, m_tileHeight, tileDpi, m_format, m_tileExtentOffset, m_metaTileFactor);
+#ifdef _DEBUG
+        Ptr<MgByteReader> mtContent = metaTile->GetImage();
+        INT32 metaTileLen = mtContent->GetLength();
+#endif
+        // splitup the meta tiles
+        for (int y = 0; y < maxY; y++)    // rows
+        {
+            for (int x = 0; x < maxX; x++)    // columns
+            {
+                // Collect the subtile from the metatile
+                Ptr<MgByteReader> img = svcRendering->RenderTileFromMetaTile(map, metaTile, m_rendererName, x, y);
+#ifdef _DEBUG
+                INT32 tileLen = img->GetLength();
+#endif          
+                if ((subTileX == x) && (subTileY == y))
+                    returnedSubtile = img;       // keep pointer for subtile which has triggered the metatile in the current thread
+                _ASSERT(x < maxX && y < maxY);
+#ifdef _DEBUG
+                std::wstringstream text;    // streams for more compact debug statements
+                text << L"(" << ACE_OS::thr_self();
+                text << L") RenderTileFromMetaTile dim:" << m_tileWidth << L"x" << m_tileHeight;
+                text << L" format:" << m_format << L" subTile(" << x << L"," << y;
+                text << L") metaTile(" << metaTileRow << L"," << metaTileColumn << L") " << metaTilePathname.c_str() << L"\n";
+                ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+                Set(img, subTilePathname[x][y]);   // store all subtiles in tile cache
+
+                // rewind the reader since setting the tile advances it to the end
+                if (img)
+                {
+                    img->Rewind();
+                }
+#ifdef _DEBUG
+                {
+                    std::wstringstream text;
+                    text << L"(" << ACE_OS::thr_self() << L") METATILE: " << (img ? L"SetTile" : L"NoTileReturned");
+                    text << "FromMetaTile(" << tileRow << L", " << tileColumn << L") ";
+                    text << subTilePathname[x][y] << L" S(" << x << L", " << y << L")\n";
+                    ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+                }
+#endif
+            }
+        }
+        break;
+    }   // end of gettile loop end MgMap scope
+
+    MG_CATCH(L"MgServerTileService.GetTile")
+
+        // remove all lockfiles or conditions
+        if (m_metaTileLockMethod)
+        {
+            metaLock.ClearLock(metaTilePathname);
+        }
+        else
+        {
+            if (NULL != lockFile)
+            {
+                MgFileUtil::DeleteFile(metaTileLockPathname, false);
+                {
+#ifdef _DEBUG
+                    std::wstringstream text;
+                    text << L"(" << ACE_OS::thr_self() << L") METATILE: DeletedLockfile(" << metaTileRow << L", " << metaTileColumn << L") "
+                        << metaTileLockPathname << L"\n";
+                    ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+                }
+            }
+        }
+
+    MG_THROW()
+
+        if (!cacheHit)
+        {
+#ifdef _DEBUG
+            std::wstringstream text;
+            text << L"(" << ACE_OS::thr_self() << L") Rendered: GetTILE(" << tileRow << L", " << tileColumn << L") "
+                << L" S(" << subTileX << L", " << subTileY << L") (" << tilePathname << L")\n";
+            ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+        }
+
+    return returnedSubtile.Detach();
 }
\ No newline at end of file

Modified: trunk/MgDev/Server/src/Services/Tile/TileCacheDefaultProvider.h
===================================================================
--- trunk/MgDev/Server/src/Services/Tile/TileCacheDefaultProvider.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Tile/TileCacheDefaultProvider.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -22,7 +22,16 @@
 class MG_SERVER_TILE_API MgTileCacheDefaultProvider : public MgTileCacheDefault
 {
 public:
-    MgTileCacheDefaultProvider(MgResourceIdentifier* tileSetId, CREFSTRING path, INT32 tileWidth, INT32 tileHeight, CREFSTRING format, bool bRenderOnly);
+    MgTileCacheDefaultProvider(MgResourceIdentifier* tileSetId, 
+                               CREFSTRING path,
+                               INT32 tileWidth,
+                               INT32 tileHeight,
+                               CREFSTRING format,
+                               bool bRenderOnly,
+                               double tileExtentOffset,
+                               CREFSTRING rendererName,
+                               INT32 metaTileFactor,
+                               INT32 metaTileLockMethod);
     virtual ~MgTileCacheDefaultProvider();
 
     virtual MgByteReader* GetTile(CREFSTRING baseMapLayerGroupName,
@@ -47,6 +56,18 @@
     virtual STRING GetBasePath();
 
 private:
+    // ---------------- Begin Metatile stuff --------------------- //
+    MgByteReader* GetMetatileForResource(MgResourceIdentifier* resource,
+                                         CREFSTRING baseMapLayerGroupName,
+                                         INT32 tileColumn,
+                                         INT32 tileRow,
+                                         INT32 scaleIndex);
+
+    // use a memory based locking scheme
+    static ACE_Recursive_Thread_Mutex sm_MetaTileMutex;
+
+    // ------------------- End Metatile stuff -------------------- //
+
     Ptr<MgResourceIdentifier> m_tilesetId;
     STRING m_path;
     INT32 m_tileWidth;
@@ -53,6 +74,10 @@
     INT32 m_tileHeight;
     STRING m_format;
     bool m_renderOnly;
+    double m_tileExtentOffset;
+    STRING m_rendererName;
+    INT32 m_metaTileFactor;
+    INT32 m_metaTileLockMethod;
 };
 
 #endif
\ No newline at end of file

Modified: trunk/MgDev/Server/src/Services/Tile/TileCacheXYZProvider.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Tile/TileCacheXYZProvider.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Tile/TileCacheXYZProvider.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -18,12 +18,25 @@
 #include "MapGuideCommon.h"
 #include "TileCacheXYZProvider.h"
 
-MgTileCacheXYZProvider::MgTileCacheXYZProvider(MgResourceIdentifier* tileSetId, CREFSTRING path, CREFSTRING format, bool bRenderOnly)
+ACE_Recursive_Thread_Mutex MgTileCacheXYZProvider::sm_MetaTileMutex;
+
+MgTileCacheXYZProvider::MgTileCacheXYZProvider(MgResourceIdentifier* tileSetId,
+                                               CREFSTRING path,
+                                               CREFSTRING format,
+                                               bool bRenderOnly,
+                                               double tileExtentOffset,
+                                               CREFSTRING rendererName,
+                                               INT32 metaTileFactor,
+                                               INT32 metaTileLockMethod)
 {
     m_tilesetId = SAFE_ADDREF(tileSetId);
     m_path = path;
     m_format = format;
     m_renderOnly = bRenderOnly;
+    m_tileExtentOffset = tileExtentOffset;
+    m_rendererName = rendererName;
+    m_metaTileFactor = metaTileFactor;
+    m_metaTileLockMethod = metaTileLockMethod;
 }
 
 MgTileCacheXYZProvider::~MgTileCacheXYZProvider()
@@ -39,7 +52,10 @@
     Ptr<MgByteReader> ret;
     MG_TRY()
 
-    ret = GetTileForResource(m_tilesetId, baseMapLayerGroupName, tileColumn, tileRow, scaleIndex);
+        if (m_metaTileFactor > 1)
+            ret = GetMetatileForResource(m_tilesetId, baseMapLayerGroupName, tileColumn, tileRow, scaleIndex);
+        else
+            ret = GetTileForResource(m_tilesetId, baseMapLayerGroupName, tileColumn, tileRow, scaleIndex);
 
     MG_CATCH_AND_THROW(L"MgTileCacheXYZProvider.GetTile")
     return ret.Detach();
@@ -70,7 +86,7 @@
         else //Assume it must be image-based at this point
         {
             // generate the tile
-            img = svcRendering->RenderTileXYZ(map, baseMapLayerGroupName, tileRow, tileColumn, scaleIndex, map->GetDisplayDpi(), m_format);
+            img = svcRendering->RenderTileXYZ(map, baseMapLayerGroupName, tileRow, tileColumn, scaleIndex, map->GetDisplayDpi(), m_format, m_tileExtentOffset);
         }
         // cache the tile
         if (!m_renderOnly)
@@ -190,4 +206,293 @@
     STRING ret;
     MgUtil::Int32ToString(tileColumn, ret);
     return ret;
+}
+
+MgByteReader* MgTileCacheXYZProvider::GetMetatileForResource(MgResourceIdentifier* resource,
+    CREFSTRING baseMapLayerGroupName,
+    INT32 tileColumn,
+    INT32 tileRow,
+    INT32 scaleIndex)
+{
+    // Must have a renderer name set
+    _ASSERT(!m_rendererName.empty());
+
+    Ptr<MgByteReader> returnedSubtile;
+    FILE* lockFile = NULL;
+    STRING tilePathname, lockPathname;
+    // metatiling vars
+    FILE* subTileLockFile[MG_METATILE_MAX_FACTOR][MG_METATILE_MAX_FACTOR];
+    STRING subTileLockPathname[MG_METATILE_MAX_FACTOR][MG_METATILE_MAX_FACTOR], subTilePathname[MG_METATILE_MAX_FACTOR][MG_METATILE_MAX_FACTOR];
+    STRING metaTileLockPathname, metaTilePathname;
+    int maxX = -1, maxY = -1, subTileX = -1, subTileY = -1, metaTileColumn = -1, metaTileRow = -1;
+    bool cacheHit = false;
+    MgMetatileLockUtil metaLock;
+
+    MG_TRY()
+
+        // Generate tile and lock pathnames for later use
+        //m_tileCache->GeneratePathnames(mapDefinition, scaleIndex, baseMapLayerGroupName,
+        //    tileColumn, tileRow, tilePathname, lockPathname, false);
+        GeneratePathNames(scaleIndex, baseMapLayerGroupName, tileColumn, tileRow, tilePathname, lockPathname, false);
+
+    subTileX = subTileY = 0;
+    metaTileColumn = tileColumn;
+    metaTileRow = tileRow;
+
+    /*
+    ////// compute additional names and control variables to deal with meta tiles:
+    // a meta tile is m_metaTileFactor^2 larger than the requested subtile so there are less tiles computed per map
+    // the metatile coords are computed from original coords modulo metatilesize
+    // e.g. with metatile=2 4 tiles (0,0)(0,1)(1,0)(1,1) get mapped to metatile(0,0)
+    subTileX = abs(tileColumn % m_metaTileFactor);
+    subTileY = abs(tileRow % m_metaTileFactor);
+    // determine left top corner of metaTile having m_metaTileFactor**2 subtiles
+    metaTileColumn = tileColumn - subTileX;
+    metaTileRow = tileRow - subTileY;
+    */
+    maxX = maxY = m_metaTileFactor;  // init control vars
+    //// iterate subtiles and create all pathnames of the created tiles
+    for (int y = 0; y < maxY; y++)    // subtile rows
+    {
+        for (int x = 0; x < maxX; x++)    // subtile columns
+        {   // Generate tile and lock pathnames (locknames are not used)
+            subTileLockFile[x][y] = NULL;
+            //m_tileCache->GeneratePathnames(mapDefinition, scaleIndex, baseMapLayerGroupName,
+            //    metaTileColumn + x, metaTileRow + y, subTilePathname[x][y], subTileLockPathname[x][y], false);
+
+            GeneratePathNames(scaleIndex, baseMapLayerGroupName,
+                metaTileColumn + x, metaTileRow + y, subTilePathname[x][y], subTileLockPathname[x][y], false);
+        }
+    }
+
+    // Generate tile and lock pathnames for meta tile in the larger grid mod xx 
+    //m_tileCache->GeneratePathnames(mapDefinition, scaleIndex, baseMapLayerGroupName,
+    //    metaTileColumn, metaTileRow, metaTilePathname, metaTileLockPathname, false);
+    GeneratePathNames(scaleIndex, baseMapLayerGroupName,
+        metaTileColumn, metaTileRow, metaTilePathname, metaTileLockPathname, false);
+
+    // m_metaTileLockMethod == 0 is using 1 remapped metalockfile for all subtiles
+    // m_metaTileLockMethod > 0 is using 1 an ace condition mapped by metatilename
+    if (m_metaTileLockMethod)
+    {
+#ifdef _DEBUG
+        {
+            std::wstringstream text;
+            text << L"(" << ACE_OS::thr_self() << L") METATILE: " << "WaitForLock("
+                << tileRow << L", " << tileColumn << L") (" << metaTilePathname << L")\n";
+            ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+        }
+#endif
+        metaLock.WaitForLock(metaTilePathname);
+    }
+    else // this is the same locking strategie as with single tiles but using one mapped metatile locking all subtile requests
+    {
+        bool fileExist = DetectTileLockFile(metaTileLockPathname);
+        if (fileExist)
+        {   // TODO: Handle the exception by displaying a tile with an error message?
+            MgFileUtil::DeleteFile(metaTileLockPathname, true);
+#ifdef _DEBUG
+            std::wstringstream text;
+            text << L"(" << ACE_OS::thr_self() << L") METATILE: " << (fileExist ? L"DeletedDanglingLockfile(" : L"NoLockfile(")
+                << metaTileRow << L", " << metaTileColumn << L") " << metaTileLockPathname << L")\n";
+            ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+        }
+    }
+    // try getting the tile from the cache first time
+    cacheHit = returnedSubtile = Get(tilePathname);
+#ifdef _DEBUG
+    std::wstringstream text;
+    text << L"(" << ACE_OS::thr_self() << (cacheHit ? L") CACHEHIT" : L") CACHEMISS") << L": GetTile("
+        << tileRow << L", " << tileColumn << L") " << tilePathname << L"\n";
+    ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+    // if the reader is NULL then the tile wasn't in the cache and we need to generate it
+    while (NULL == returnedSubtile) // while for if to be able to use break to skip to end;
+    {
+        Ptr<MgMap> map;
+
+        // Protect the serialized MgMap cache with a mutex.  Stream reading is not
+        // thread safe so we need to deserialize the map within the mutex to ensure
+        // that a Rewind() is not called in the middle of a Deserialize().
+        // Lockfile test and creation is in same protected scope.
+        { // ------------------------ Locking Context
+            // Attempt to get the mapcache mutex .
+            ACE_MT(ACE_GUARD_RETURN(ACE_Recursive_Thread_Mutex, ace_mon, sm_MetaTileMutex, NULL));
+            if (m_metaTileLockMethod)
+            {
+                // we DONT need the second lock test here in a signal/wait algorithm
+    //          m_tileCache->WaitForLock(metaTilePathname);
+            }
+            else
+            {
+                if (DetectTileLockFile(metaTileLockPathname)) //
+                {
+                    MgStringCollection arguments;
+                    arguments.Add(metaTileLockPathname);
+                    throw new MgFileIoException(L"MgServerTileService.GetTile",
+                        __LINE__, __WFILE__, &arguments, L"MgUnableToLockMetaTileFile", NULL);
+                }
+            }
+
+            // try getting the tile from the cache second time ???????????????????????????????????????????
+            cacheHit = returnedSubtile = Get(tilePathname);
+
+            if (NULL != returnedSubtile)
+            {
+#ifdef _DEBUG
+                std::wstringstream text;
+                text << L"(" << ACE_OS::thr_self() << L") CACHEHIT2: GetTile(" << tileRow << L", " << tileColumn << L") " << tilePathname << L"\n";
+                ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif        
+                break;  /////////////////////////////////// tile was in tileCache .. done.
+            }
+            else {
+#ifdef _DEBUG
+                std::wstringstream text;
+                text << L"(" << ACE_OS::thr_self() << L") CACHEMISS2: GetTile(" << tileRow << L", " << tileColumn << L") " << tilePathname << L"\n";
+                ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+            }
+            // Create the directory structure all subtiles!
+            for (int y = 0; y < maxY; y++)    // rows
+            {
+                for (int x = 0; x < maxX; x++)    // columns
+                {
+                    //m_tileCache->CreateFullPath(mapDefinition, scaleIndex, baseMapLayerGroupName, 
+                    //    metaTileColumn + x, metaTileRow + y);
+                    CreateFullPath(GetBasePath(), scaleIndex, baseMapLayerGroupName, metaTileColumn + x, metaTileRow + y);
+                }
+            }
+            // ==================================================  LOCKING
+            if (m_metaTileLockMethod)
+            {
+                metaLock.LockTile(metaTilePathname);    // ace lock once per metatile
+            }
+            else
+            {
+                // Create the lock file 
+                lockFile = ACE_OS::fopen(MG_WCHAR_TO_TCHAR(metaTileLockPathname), ACE_TEXT("wb"));
+
+                if (NULL == lockFile)
+                {
+                    MgStringCollection arguments;
+                    arguments.Add(metaTileLockPathname);
+                    throw new MgFileIoException(L"MgServerTileService.GetTile",
+                        __LINE__, __WFILE__, &arguments, L"MgUnableToCreateLockFile", NULL);
+                }
+                else
+                {
+                    ACE_OS::fclose(lockFile); // and close it right away.
+#ifdef _DEBUG
+                    std::wstringstream text;
+                    text << L"(" << ACE_OS::thr_self() << L") METATILE: CreatedLockfile(" << metaTileRow << L", " << metaTileColumn << L") "
+                        << metaTileLockPathname << L")\n";
+                    ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+                }
+            }
+            //=========================================================================
+            GetMapFromDefinition(resource, -1 /* Don't set view scale from index for XYZ*/, map, false);
+        }   // end of mutex scope
+
+        // Render the larger meta tile but do not cache it yet! (m_metaTileFactor [prohibits caching in GetTile)
+        STRING metaTileName = L"META" + metaTilePathname;
+#ifdef _DEBUG
+        std::wstringstream text;
+        text << L"(" << ACE_OS::thr_self() << L") METATILE: RenderMetaTile(" << metaTileRow << L", " << metaTileColumn << L") " << metaTileName << " using map 0x" << map << L"\n";
+        ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+        MgServiceManager* serviceMan = MgServiceManager::GetInstance();
+        _ASSERT(NULL != serviceMan);
+        Ptr<MgRenderingService> svcRendering = dynamic_cast<MgRenderingService*>(
+            serviceMan->RequestService(MgServiceType::RenderingService));
+        _ASSERT(NULL != svcRendering);
+
+        //Ptr<MgByteReader> metaTileBitMap = GetTile(metaTileName, map, scaleIndex, baseMapLayerGroupName, metaTileColumn, metaTileRow);
+        INT32 tileDpi = map->GetDisplayDpi();
+        Ptr<MgMetatile> metaTile = svcRendering->RenderMetatileXYZ(map, baseMapLayerGroupName, metaTileRow, metaTileColumn, scaleIndex, tileDpi, m_format, m_tileExtentOffset, m_metaTileFactor);
+#ifdef _DEBUG
+        Ptr<MgByteReader> mtContent = metaTile->GetImage();
+        INT32 metaTileLen = mtContent->GetLength();
+#endif
+        // splitup the meta tiles
+        for (int y = 0; y < maxY; y++)    // rows
+        {
+            for (int x = 0; x < maxX; x++)    // columns
+            {
+                // Collect the subtile from the metatile (we flip x/y for XYZ)
+                Ptr<MgByteReader> img = svcRendering->RenderTileFromMetaTile(map, metaTile, m_rendererName, x, y);
+#ifdef _DEBUG
+                INT32 tileLen = img->GetLength();
+#endif          
+                if ((subTileX == x) && (subTileY == y))
+                    returnedSubtile = img;       // keep pointer for subtile which has triggered the metatile in the current thread
+                _ASSERT(x < maxX && y < maxY);
+#ifdef _DEBUG
+                std::wstringstream text;    // streams for more compact debug statements
+                text << L"(" << ACE_OS::thr_self();
+                text << L") RenderTileFromMetaTile dim:" << 256 << L"x" << 256;
+                text << L" format:" << m_format << L" subTile(" << x << L"," << y;
+                text << L") metaTile(" << metaTileRow << L"," << metaTileColumn << L") " << metaTilePathname.c_str() << L"\n";
+                ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+                STRING tilePath = subTilePathname[y][x];
+                Set(img, tilePath);   // store all subtiles in tile cache
+
+                // rewind the reader since setting the tile advances it to the end
+                if (img)
+                {
+                    img->Rewind();
+                }
+#ifdef _DEBUG
+                {
+                    std::wstringstream text;
+                    text << L"(" << ACE_OS::thr_self() << L") METATILE: " << (img ? L"SetTile" : L"NoTileReturned");
+                    text << "FromMetaTile(" << tileRow << L", " << tileColumn << L") ";
+                    text << tilePath << L" S(" << x << L", " << y << L")\n";
+                    ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+                }
+#endif
+            }
+        }
+        break;
+    }   // end of gettile loop end MgMap scope
+
+    MG_CATCH(L"MgServerTileService.GetTile")
+
+        // remove all lockfiles or conditions
+        if (m_metaTileLockMethod)
+        {
+            metaLock.ClearLock(metaTilePathname);
+        }
+        else
+        {
+            if (NULL != lockFile)
+            {
+                MgFileUtil::DeleteFile(metaTileLockPathname, false);
+                {
+#ifdef _DEBUG
+                    std::wstringstream text;
+                    text << L"(" << ACE_OS::thr_self() << L") METATILE: DeletedLockfile(" << metaTileRow << L", " << metaTileColumn << L") "
+                        << metaTileLockPathname << L"\n";
+                    ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+                }
+            }
+        }
+
+    MG_THROW()
+
+        if (!cacheHit)
+        {
+#ifdef _DEBUG
+            std::wstringstream text;
+            text << L"(" << ACE_OS::thr_self() << L") Rendered: GetTILE(" << tileRow << L", " << tileColumn << L") "
+                << L" S(" << subTileX << L", " << subTileY << L") (" << tilePathname << L")\n";
+            ACE_DEBUG((LM_DEBUG, text.str().c_str()));
+#endif
+        }
+
+    return returnedSubtile.Detach();
 }
\ No newline at end of file

Modified: trunk/MgDev/Server/src/Services/Tile/TileCacheXYZProvider.h
===================================================================
--- trunk/MgDev/Server/src/Services/Tile/TileCacheXYZProvider.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/Services/Tile/TileCacheXYZProvider.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -22,7 +22,14 @@
 class MG_SERVER_TILE_API MgTileCacheXYZProvider : public MgTileCacheDefault
 {
 public:
-    MgTileCacheXYZProvider(MgResourceIdentifier* tileSetId, CREFSTRING path, CREFSTRING format, bool bRenderOnly);
+    MgTileCacheXYZProvider(MgResourceIdentifier* tileSetId,
+                           CREFSTRING path,
+                           CREFSTRING format,
+                           bool bRenderOnly,
+                           double tileExtentOffset,
+                           CREFSTRING rendererName,
+                           INT32 metaTileFactor,
+                           INT32 metaTileLockMethod);
     virtual ~MgTileCacheXYZProvider();
 
     virtual MgByteReader* GetTile(CREFSTRING baseMapLayerGroupName,
@@ -53,10 +60,27 @@
     virtual STRING GetTileFileExtension();
 
 private:
+    // ---------------- Begin Metatile stuff --------------------- //
+    MgByteReader* GetMetatileForResource(MgResourceIdentifier* resource,
+        CREFSTRING baseMapLayerGroupName,
+        INT32 tileColumn,
+        INT32 tileRow,
+        INT32 scaleIndex);
+
+    // use a memory based locking scheme
+    static ACE_Recursive_Thread_Mutex sm_MetaTileMutex;
+
+    // ------------------- End Metatile stuff -------------------- //
+
     Ptr<MgResourceIdentifier> m_tilesetId;
     STRING m_path;
     STRING m_format;
     bool m_renderOnly;
+    double m_tileExtentOffset;
+    INT32 m_metaTileFactor;
+    INT32 m_metaTileLockMethod;
+
+    STRING m_rendererName;
 };
 
 #endif
\ No newline at end of file

Modified: trunk/MgDev/Server/src/UnitTesting/TestRenderingService.cpp
===================================================================
--- trunk/MgDev/Server/src/UnitTesting/TestRenderingService.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/UnitTesting/TestRenderingService.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -24,6 +24,8 @@
 //#include "GDRenderer.h"
 //#include "AGGRenderer.h"
 #include "FoundationDefs.h"
+#include "SE_Renderer.h"
+
 const STRING TEST_LOCALE = L"en";
 
 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(TestRenderingService, "TestRenderingService");
@@ -1713,21 +1715,42 @@
     }
 }
 
-void TestRenderingService::TestCase_RenderTileBaseline()
+void TestRenderingService::TestCase_RenderMetatile(CREFSTRING imageFormat, CREFSTRING extension)
 {
     try
     {
         Ptr<MgMap> map = CreateTestTiledMap();
         map->SetViewScale(12500.0);
-        Ptr<MgByteReader> tile4_6 = m_svcRendering->RenderTile(map, L"BaseLayers", 4, 6);
-        Ptr<MgByteReader> tile4_7 = m_svcRendering->RenderTile(map, L"BaseLayers", 4, 7);
-        Ptr<MgByteReader> tile5_6 = m_svcRendering->RenderTile(map, L"BaseLayers", 5, 6);
-        Ptr<MgByteReader> tile5_7 = m_svcRendering->RenderTile(map, L"BaseLayers", 5, 7);
+        Ptr<MgByteReader> tile4_6_baseline = m_svcRendering->RenderTile(map, L"BaseLayers", 4, 6, MgTileParameters::tileWidth, MgTileParameters::tileHeight, MgTileParameters::tileDPI, imageFormat);
+        Ptr<MgByteReader> tile4_7_baseline = m_svcRendering->RenderTile(map, L"BaseLayers", 4, 7, MgTileParameters::tileWidth, MgTileParameters::tileHeight, MgTileParameters::tileDPI, imageFormat);
+        Ptr<MgByteReader> tile5_6_baseline = m_svcRendering->RenderTile(map, L"BaseLayers", 5, 6, MgTileParameters::tileWidth, MgTileParameters::tileHeight, MgTileParameters::tileDPI, imageFormat);
+        Ptr<MgByteReader> tile5_7_baseline = m_svcRendering->RenderTile(map, L"BaseLayers", 5, 7, MgTileParameters::tileWidth, MgTileParameters::tileHeight, MgTileParameters::tileDPI, imageFormat);
 
-        tile4_6->ToFile(L"../UnitTestFiles/RenderTile_4_6_Baseline.png");
-        tile4_7->ToFile(L"../UnitTestFiles/RenderTile_4_7_Baseline.png");
-        tile5_6->ToFile(L"../UnitTestFiles/RenderTile_5_6_Baseline.png");
-        tile5_7->ToFile(L"../UnitTestFiles/RenderTile_5_7_Baseline.png");
+        tile4_6_baseline->ToFile(GetPath(L"../UnitTestFiles/RenderTile_4_6_Baseline", imageFormat, extension));
+        tile4_7_baseline->ToFile(GetPath(L"../UnitTestFiles/RenderTile_4_7_Baseline", imageFormat, extension));
+        tile5_6_baseline->ToFile(GetPath(L"../UnitTestFiles/RenderTile_5_6_Baseline", imageFormat, extension));
+        tile5_7_baseline->ToFile(GetPath(L"../UnitTestFiles/RenderTile_5_7_Baseline", imageFormat, extension));
+
+        MgRenderingService* renderSvc = dynamic_cast<MgRenderingService*>(m_svcRendering.p);
+        CPPUNIT_ASSERT(NULL != renderSvc);
+
+        // Render a 2x2 metatile which should cover the same tiles as the baseline test.
+        INT32 metaTileFactor = 2;
+        Ptr<MgMetatile> metaTile = renderSvc->RenderMetatile(map, L"BaseLayers", 4, 6, MgTileParameters::tileWidth, MgTileParameters::tileHeight, MgTileParameters::tileDPI, imageFormat, MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset, metaTileFactor);
+        //metaTile->ToFile(L"../UnitTestFiles/RenderTile_Metatile at 4_6.png");
+        //CPPUNIT_ASSERT(metaTile->IsRewindable());
+        //metaTile->Rewind();
+        STRING rendererName = L"AGG";
+        Ptr<MgByteReader> tile4_6 = renderSvc->RenderTileFromMetaTile(map, metaTile, rendererName, 0, 0);
+        Ptr<MgByteReader> tile4_7 = renderSvc->RenderTileFromMetaTile(map, metaTile, rendererName, 0, 1);
+        Ptr<MgByteReader> tile5_6 = renderSvc->RenderTileFromMetaTile(map, metaTile, rendererName, 1, 0);
+        Ptr<MgByteReader> tile5_7 = renderSvc->RenderTileFromMetaTile(map, metaTile, rendererName, 1, 1);
+
+
+        tile4_6->ToFile(GetPath(L"../UnitTestFiles/RenderTile_4_6_Metatiled", imageFormat, extension));
+        tile4_7->ToFile(GetPath(L"../UnitTestFiles/RenderTile_4_7_Metatiled", imageFormat, extension));
+        tile5_6->ToFile(GetPath(L"../UnitTestFiles/RenderTile_5_6_Metatiled", imageFormat, extension));
+        tile5_7->ToFile(GetPath(L"../UnitTestFiles/RenderTile_5_7_Metatiled", imageFormat, extension));
     }
     catch (MgException* e)
     {
@@ -1737,6 +1760,50 @@
     }
 }
 
+void TestRenderingService::TestCase_RenderXYZMetatile(CREFSTRING imageFormat, CREFSTRING extension)
+{
+    try
+    {
+        Ptr<MgMap> map = CreateTestTiledMap();
+        map->SetViewScale(12500.0);
+        Ptr<MgByteReader> tile_16798_23891_baseline = m_svcRendering->RenderTileXYZ(map, L"BaseLayers", 16798, 23891, 16, MgTileParameters::tileDPI, imageFormat);
+        Ptr<MgByteReader> tile_16799_23891_baseline = m_svcRendering->RenderTileXYZ(map, L"BaseLayers", 16799, 23891, 16, MgTileParameters::tileDPI, imageFormat);
+        Ptr<MgByteReader> tile_16798_23892_baseline = m_svcRendering->RenderTileXYZ(map, L"BaseLayers", 16798, 23892, 16, MgTileParameters::tileDPI, imageFormat);
+        Ptr<MgByteReader> tile_16799_23892_baseline = m_svcRendering->RenderTileXYZ(map, L"BaseLayers", 16799, 23892, 16, MgTileParameters::tileDPI, imageFormat);
+
+        tile_16798_23891_baseline->ToFile(GetPath(L"../UnitTestFiles/RenderTileXYZ_16798_23891_16_Baseline", imageFormat, extension));
+        tile_16799_23891_baseline->ToFile(GetPath(L"../UnitTestFiles/RenderTileXYZ_16799_23891_16_Baseline", imageFormat, extension));
+        tile_16798_23892_baseline->ToFile(GetPath(L"../UnitTestFiles/RenderTileXYZ_16798_23892_16_Baseline", imageFormat, extension));
+        tile_16799_23892_baseline->ToFile(GetPath(L"../UnitTestFiles/RenderTileXYZ_16799_23892_16_Baseline", imageFormat, extension));
+
+        MgRenderingService* renderSvc = dynamic_cast<MgRenderingService*>(m_svcRendering.p);
+        CPPUNIT_ASSERT(NULL != renderSvc);
+
+        // Render a 2x2 metatile which should cover the same tiles as the baseline test.
+        INT32 metaTileFactor = 2;
+        Ptr<MgMetatile> metaTile = renderSvc->RenderMetatileXYZ(map, L"BaseLayers", 16798, 23891, 16, MgTileParameters::tileDPI, imageFormat, MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset, metaTileFactor);
+        //metaTile->ToFile(L"../UnitTestFiles/RenderTileXYZ_Metatile at 16798_23891_16.png");
+        //CPPUNIT_ASSERT(metaTile->IsRewindable());
+        //metaTile->Rewind();
+        STRING rendererName = L"AGG";
+        Ptr<MgByteReader> tile_16798_23891 = renderSvc->RenderTileFromMetaTile(map, metaTile, rendererName, 0, 0);
+        Ptr<MgByteReader> tile_16799_23891 = renderSvc->RenderTileFromMetaTile(map, metaTile, rendererName, 1, 0);
+        Ptr<MgByteReader> tile_16798_23892 = renderSvc->RenderTileFromMetaTile(map, metaTile, rendererName, 0, 1);
+        Ptr<MgByteReader> tile_16799_23892 = renderSvc->RenderTileFromMetaTile(map, metaTile, rendererName, 1, 1);
+
+        tile_16798_23891->ToFile(GetPath(L"../UnitTestFiles/RenderTileXYZ_16798_23891_16_Metatiled", imageFormat, extension));
+        tile_16799_23891->ToFile(GetPath(L"../UnitTestFiles/RenderTileXYZ_16799_23891_16_Metatiled", imageFormat, extension));
+        tile_16798_23892->ToFile(GetPath(L"../UnitTestFiles/RenderTileXYZ_16798_23892_16_Metatiled", imageFormat, extension));
+        tile_16799_23892->ToFile(GetPath(L"../UnitTestFiles/RenderTileXYZ_16799_23892_16_Metatiled", imageFormat, extension));
+    }
+    catch (MgException* e)
+    {
+        STRING message = e->GetDetails(TEST_LOCALE);
+        SAFE_RELEASE(e);
+        CPPUNIT_FAIL(MG_WCHAR_TO_CHAR(message.c_str()));
+    }
+}
+
 void TestRenderingService::TestCase_RenderTile(CREFSTRING imageFormat, CREFSTRING extension)
 {
     try

Modified: trunk/MgDev/Server/src/UnitTesting/TestRenderingService.h
===================================================================
--- trunk/MgDev/Server/src/UnitTesting/TestRenderingService.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/UnitTesting/TestRenderingService.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -25,8 +25,16 @@
     CPPUNIT_TEST_SUITE(TestRenderingService);
     CPPUNIT_TEST(TestStart); // This must be the very first unit test
 
-    CPPUNIT_TEST(TestCase_RenderTileBaseline);
-
+    CPPUNIT_TEST(TestCase_RenderXYZMetatilePNG);
+    CPPUNIT_TEST(TestCase_RenderXYZMetatilePNG8);
+    CPPUNIT_TEST(TestCase_RenderXYZMetatileJPG);
+    CPPUNIT_TEST(TestCase_RenderXYZMetatileGIF);
+    
+    CPPUNIT_TEST(TestCase_RenderMetatilePNG);
+    CPPUNIT_TEST(TestCase_RenderMetatilePNG8);
+    CPPUNIT_TEST(TestCase_RenderMetatileJPG);
+    CPPUNIT_TEST(TestCase_RenderMetatileGIF);
+    
     CPPUNIT_TEST(TestCase_StylizationFunctionsPNG);
 
     CPPUNIT_TEST(TestCase_SymbologyPointsPNG);
@@ -145,7 +153,6 @@
     CPPUNIT_TEST(TestCase_RenderTileXYZ_JPG);
 
     CPPUNIT_TEST(TestCase_QueryFeatures);
-
     //CPPUNIT_TEST(TestCase_RendererPerformance);
 
     CPPUNIT_TEST(TestEnd); // This must be the very last unit test
@@ -161,7 +168,8 @@
     void TestEnd();
 
     void TestCase_QueryFeatures();
-    void TestCase_RenderTileBaseline();
+    void TestCase_RenderMetatile(CREFSTRING imageFormat, CREFSTRING extension);
+    void TestCase_RenderXYZMetatile(CREFSTRING imageFormat, CREFSTRING extension);
     void TestCase_RenderTile(CREFSTRING imageFormat, CREFSTRING extension);
     void TestCase_RenderTileXYZ(CREFSTRING imageFormat, CREFSTRING extension);
 
@@ -305,6 +313,16 @@
 
     //void TestCase_RendererPerformance();
 
+    void TestCase_RenderMetatilePNG() { TestCase_RenderMetatile(L"PNG", L"png"); }
+    void TestCase_RenderMetatilePNG8() { TestCase_RenderMetatile(L"PNG8", L"png"); }
+    void TestCase_RenderMetatileJPG() { TestCase_RenderMetatile(L"JPG", L"jpg"); }
+    void TestCase_RenderMetatileGIF() { TestCase_RenderMetatile(L"GIF", L"gif"); }
+
+    void TestCase_RenderXYZMetatilePNG() { TestCase_RenderXYZMetatile(L"PNG", L"png"); }
+    void TestCase_RenderXYZMetatilePNG8() { TestCase_RenderXYZMetatile(L"PNG8", L"png"); }
+    void TestCase_RenderXYZMetatileJPG() { TestCase_RenderXYZMetatile(L"JPG", L"jpg"); }
+    void TestCase_RenderXYZMetatileGIF() { TestCase_RenderXYZMetatile(L"GIF", L"gif"); }
+
 private:
     MgMap* CreateTestMap();
     MgMap* CreateTestTiledMap();

Modified: trunk/MgDev/Server/src/UnitTesting/TestTileService.cpp
===================================================================
--- trunk/MgDev/Server/src/UnitTesting/TestTileService.cpp	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/UnitTesting/TestTileService.cpp	2019-05-27 12:24:20 UTC (rev 9510)
@@ -23,6 +23,7 @@
 #include "ServerSiteService.h"
 #include "../Common/Manager/FdoConnectionManager.h"
 #include "FoundationDefs.h"
+#include "ServerRenderingService.h"
 #define PATH_LEN 512
 
 // determines the number of requests to make, as a factor of the number of tiles
@@ -138,6 +139,21 @@
         Ptr<MgByteReader> tsdrdr2 = tsdsrc2->GetReader();
         m_svcResource->SetResource(tilesetres2, tsdrdr2, NULL);
 
+        Ptr<MgResourceIdentifier> tilesetres3 = new MgResourceIdentifier(L"Library://UnitTests/TileSets/Sheboygan_Metatiled.TileSetDefinition");
+        Ptr<MgByteSource> tsdsrc3 = new MgByteSource(L"../UnitTestFiles/UT_BaseMap_Metatiled.tsd", false);
+        Ptr<MgByteReader> tsdrdr3 = tsdsrc3->GetReader();
+        m_svcResource->SetResource(tilesetres3, tsdrdr3, NULL);
+
+        Ptr<MgResourceIdentifier> tilesetres4 = new MgResourceIdentifier(L"Library://UnitTests/TileSets/XYZ_Metatiled.TileSetDefinition");
+        Ptr<MgByteSource> tsdsrc4 = new MgByteSource(L"../UnitTestFiles/UT_XYZ_Metatiled.tsd", false);
+        Ptr<MgByteReader> tsdrdr4 = tsdsrc4->GetReader();
+        m_svcResource->SetResource(tilesetres4, tsdrdr4, NULL);
+
+        Ptr<MgResourceIdentifier> tilesetres5 = new MgResourceIdentifier(L"Library://UnitTests/TileSets/Sheboygan_OrigSize.TileSetDefinition");
+        Ptr<MgByteSource> tsdsrc5 = new MgByteSource(L"../UnitTestFiles/UT_BaseMap_OrigSize.tsd", false);
+        Ptr<MgByteReader> tsdrdr5 = tsdsrc5->GetReader();
+        m_svcResource->SetResource(tilesetres5, tsdrdr5, NULL);
+
         // publish the layer definitions
         Ptr<MgResourceIdentifier> ldfres1 = new MgResourceIdentifier(L"Library://UnitTests/Layers/RoadCenterLines.LayerDefinition");
         Ptr<MgByteSource> ldfsrc1 = new MgByteSource(L"../UnitTestFiles/UT_RoadCenterLines.ldf", false);
@@ -185,6 +201,7 @@
 
         // set up temporary directory for tile images
         MgFileUtil::CreateDirectory(L"./temp_tiles", false);
+        MgFileUtil::CreateDirectory(L"../UnitTestFiles/TileCaches", false);
     }
     catch (MgException* e)
     {
@@ -222,6 +239,18 @@
         Ptr<MgResourceIdentifier> tilesetres1 = new MgResourceIdentifier(L"Library://UnitTests/TileSets/Sheboygan.TileSetDefinition");
         m_svcResource->DeleteResource(tilesetres1);
 
+        Ptr<MgResourceIdentifier> tilesetres2 = new MgResourceIdentifier(L"Library://UnitTests/TileSets/XYZ.TileSetDefinition");
+        m_svcResource->DeleteResource(tilesetres2);
+
+        Ptr<MgResourceIdentifier> tilesetres3 = new MgResourceIdentifier(L"Library://UnitTests/TileSets/Sheboygan_Metatiled.TileSetDefinition");
+        m_svcResource->DeleteResource(tilesetres3);
+
+        Ptr<MgResourceIdentifier> tilesetres4 = new MgResourceIdentifier(L"Library://UnitTests/TileSets/XYZ_Metatiled.TileSetDefinition");
+        m_svcResource->DeleteResource(tilesetres4);
+
+        Ptr<MgResourceIdentifier> tilesetres5 = new MgResourceIdentifier(L"Library://UnitTests/TileSets/Sheboygan_OrigSize.TileSetDefinition");
+        m_svcResource->DeleteResource(tilesetres5);
+
         // delete the layer definitions
         Ptr<MgResourceIdentifier> ldfres1 = new MgResourceIdentifier(L"Library://UnitTests/Layers/RoadCenterLines.LayerDefinition");
         m_svcResource->DeleteResource(ldfres1);
@@ -1720,6 +1749,157 @@
     }
 }
 
+void TestTileService::TestCase_GetMetatileSingle()
+{
+    try
+    {
+        INT32 col = 28;
+        INT32 row = 29;
+        INT32 scale = 3;
+
+        Ptr<MgByteReader> tile;
+        Ptr<MgResourceIdentifier> tsId;
+
+        tsId = new MgResourceIdentifier(L"Library://UnitTests/TileSets/Sheboygan_OrigSize.TileSetDefinition");
+        m_svcTile->ClearCache(tsId);
+        tile = m_svcTile->GetTile(tsId, L"BaseLayers", col, row, scale);
+        tile->ToFile(L"../UnitTestFiles/GetMetatileSingle_Baseline.png");
+
+        INT32 metaTileFactor = 4;
+        //Render full 4x4 tiles to check we're generating the same file paths from metatile
+        for (INT32 x = col; x < col + metaTileFactor; x++)
+        {
+            for (INT32 y = row; y < row + metaTileFactor; y++)
+            {
+                tile = m_svcTile->GetTile(tsId, L"BaseLayers", x, y, scale);
+            }
+        }
+
+        Ptr<MgMap> map = CreateMapLinked(L"MetaTileBaseline");
+        map->SetViewScale(3125);
+        Ptr<MgRenderingService> renderSvc = dynamic_cast<MgRenderingService*>(m_siteConnection->CreateService(MgServiceType::RenderingService));
+        CPPUNIT_ASSERT(NULL != renderSvc);
+
+        MgFileUtil::CreateDirectory(L"../UnitTestFiles/GetMetatileSingle_Baseline");
+
+        Ptr<MgMetatile> metaTile = renderSvc->RenderMetatile(map, L"BaseLayers", col, row, MgTileParameters::tileWidth, MgTileParameters::tileHeight, MgTileParameters::tileDPI, MgImageFormats::Png, MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset, metaTileFactor);
+        Ptr<MgByteReader> mtContent = metaTile->GetImage();
+        INT64 metaTileLen = mtContent->GetLength();
+        for (INT32 x = 0; x < metaTile->GetMetaTilingFactor(); x++)
+        {
+            for (INT32 y = 0; y < metaTile->GetMetaTilingFactor(); y++)
+            {
+                Ptr<MgByteReader> img = renderSvc->RenderTileFromMetaTile(map, metaTile, L"AGG", x, y);
+                INT64 tileLen = img->GetLength();
+                STRING fileName = L"../UnitTestFiles/GetMetatileSingle_Baseline/";
+                STRING s;
+                MgUtil::Int32ToString(y, s);
+                fileName += s;
+                fileName += L"y_";
+                MgUtil::Int32ToString(x, s);
+                fileName += s;
+                fileName += L"x.png";
+                img->ToFile(fileName);
+            }
+        }
+
+        tsId = new MgResourceIdentifier(L"Library://UnitTests/TileSets/Sheboygan_Metatiled.TileSetDefinition");
+        m_svcTile->ClearCache(tsId);
+        tile = m_svcTile->GetTile(tsId, L"BaseLayers", col, row, scale);
+        tile->ToFile(L"../UnitTestFiles/GetMetatileSingle.png");
+    }
+    catch (MgException* e)
+    {
+        STRING message = e->GetDetails(TEST_LOCALE);
+        SAFE_RELEASE(e);
+        CPPUNIT_FAIL(MG_WCHAR_TO_CHAR(message.c_str()));
+    }
+    catch (...)
+    {
+        throw;
+    }
+}
+
+void TestTileService::TestCase_GetMetatileXYZSingle()
+{
+    try
+    {
+        INT32 x = 23892;
+        INT32 y = 16798;
+        INT32 z = 16;
+
+        Ptr<MgByteReader> tile;
+        Ptr<MgResourceIdentifier> tsId;
+
+        tsId = new MgResourceIdentifier(L"Library://UnitTests/TileSets/XYZ.TileSetDefinition");
+        m_svcTile->ClearCache(tsId);
+        tile = m_svcTile->GetTile(tsId, L"BaseLayers", x, y, z);
+        tile->ToFile(L"../UnitTestFiles/GetMetatileXYZSingle_Baseline.png");
+
+        INT32 metaTileFactor = 4;
+        //Render full 4x4 tiles to check we're generating the same file paths from metatile
+        for (INT32 x1 = x; x1 < x + metaTileFactor; x1++)
+        {
+            for (INT32 y1 = y; y1 < y + metaTileFactor; y1++)
+            {
+                tile = m_svcTile->GetTile(tsId, L"BaseLayers", x1, y1, z);
+            }
+        }
+
+        Ptr<MgCoordinateSystemFactory> csFactory = new MgCoordinateSystemFactory();
+        STRING ovCsvWkt = csFactory->ConvertCoordinateSystemCodeToWkt(L"LL84");
+
+        Ptr<MgMap> map = CreateMapLinked2(L"MetaTileBaseline", ovCsvWkt);
+        map->SetViewScale(3125);
+        Ptr<MgRenderingService> renderSvc = dynamic_cast<MgRenderingService*>(m_siteConnection->CreateService(MgServiceType::RenderingService));
+        CPPUNIT_ASSERT(NULL != renderSvc);
+
+        MgFileUtil::CreateDirectory(L"../UnitTestFiles/GetMetatileSingleXYZ_Baseline");
+
+        //NOTE: When rendering the XYZ tile we need to flip x/y
+        INT32 mtX = y;
+        INT32 mtY = x;
+
+        Ptr<MgMetatile> metaTile = renderSvc->RenderMetatileXYZ(map, L"BaseLayers", mtX, mtY, z, MgTileParameters::tileDPI, MgImageFormats::Png, MgConfigProperties::DefaultRenderingServicePropertyTileExtentOffset, metaTileFactor);
+        Ptr<MgByteReader> mtContent = metaTile->GetImage();
+        INT64 metaTileLen = mtContent->GetLength();
+        INT32 mtFactor = metaTile->GetMetaTilingFactor();
+        for (INT32 x1 = 0; x1 < mtFactor; x1++)
+        {
+            for (INT32 y1 = 0; y1 < mtFactor; y1++)
+            {
+                Ptr<MgByteReader> img = renderSvc->RenderTileFromMetaTile(map, metaTile, L"AGG", x1, y1);
+                INT64 tileLen = img->GetLength();
+                STRING fileName = L"../UnitTestFiles/GetMetatileSingleXYZ_Baseline/";
+                STRING s;
+                MgUtil::Int32ToString(y1 + y, s);
+                fileName += s;
+                fileName += L"y_";
+                MgUtil::Int32ToString(x1 + x, s);
+                fileName += s;
+                fileName += L"x.png";
+                img->ToFile(fileName);
+            }
+        }
+
+        tsId = new MgResourceIdentifier(L"Library://UnitTests/TileSets/XYZ_Metatiled.TileSetDefinition");
+        m_svcTile->ClearCache(tsId);
+        tile = m_svcTile->GetTile(tsId, L"BaseLayers", x, y, z);
+        tile->ToFile(L"../UnitTestFiles/GetMetatileXYZSingle.png");
+
+    }
+    catch (MgException* e)
+    {
+        STRING message = e->GetDetails(TEST_LOCALE);
+        SAFE_RELEASE(e);
+        CPPUNIT_FAIL(MG_WCHAR_TO_CHAR(message.c_str()));
+    }
+    catch (...)
+    {
+        throw;
+    }
+}
+
 ////////////////////////////////////////////////////////////////
 /// Helpers
 ////////////////////////////////////////////////////////////////
@@ -1774,7 +1954,7 @@
     return map;
 }
 
-MgMap* TestTileService::CreateMapLinked2(CREFSTRING mapName)
+MgMap* TestTileService::CreateMapLinked2(CREFSTRING mapName, CREFSTRING ovCsWkt)
 {
     // set a default name if not supplied
     STRING name = (mapName.empty()) ? L"UnitTestBaseMapLinked" : mapName;
@@ -1809,6 +1989,12 @@
             break;
         }
     }
+
+    if (!ovCsWkt.empty())
+    {
+        mdf->SetCoordinateSystem(ovCsWkt);
+    }
+
     // Fix up extents too
     mdf->SetExtents(tsd->GetExtents());
 

Modified: trunk/MgDev/Server/src/UnitTesting/TestTileService.h
===================================================================
--- trunk/MgDev/Server/src/UnitTesting/TestTileService.h	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Server/src/UnitTesting/TestTileService.h	2019-05-27 12:24:20 UTC (rev 9510)
@@ -24,6 +24,10 @@
 {
     CPPUNIT_TEST_SUITE(TestTileService);
     CPPUNIT_TEST(TestStart); // This must be the very first unit test
+    
+    CPPUNIT_TEST(TestCase_GetMetatileSingle);
+    CPPUNIT_TEST(TestCase_GetMetatileXYZSingle);
+
     CPPUNIT_TEST(TestCase_GetTileProviders);
     CPPUNIT_TEST(TestCase_MgMap_Inline);
     CPPUNIT_TEST(TestCase_MgMap_Linked);
@@ -32,6 +36,8 @@
     CPPUNIT_TEST(TestCase_MgMapFromXYZTileSetLoose);
     CPPUNIT_TEST(TestCase_MgMapFromTileSet);
     CPPUNIT_TEST(TestCase_GetTile);
+    
+
     CPPUNIT_TEST(TestCase_SetTile);
     CPPUNIT_TEST(TestCase_GetSetTile);
     CPPUNIT_TEST(TestCase_ClearCache);
@@ -38,6 +44,7 @@
     CPPUNIT_TEST(TestCase_GetTileLinked);
     CPPUNIT_TEST(TestCase_GetTileXYZ);
     CPPUNIT_TEST(TestCase_ClearCacheLinked);
+
     CPPUNIT_TEST(TestEnd); // This must be the very last unit test
     CPPUNIT_TEST_SUITE_END();
 
@@ -65,10 +72,13 @@
     void TestCase_GetTileLinked();
     void TestCase_ClearCacheLinked();
 
+    void TestCase_GetMetatileSingle();
+    void TestCase_GetMetatileXYZSingle();
+
 private:
     MgMap* CreateMap(CREFSTRING mapName = L"");
     MgMap* CreateMapLinked(CREFSTRING mapName = L"");
-    MgMap* CreateMapLinked2(CREFSTRING mapName = L"");
+    MgMap* CreateMapLinked2(CREFSTRING mapName = L"", CREFSTRING ovCsWkt = L"");
     INT32 Rand(INT32 n);
 
 private:

Copied: trunk/MgDev/UnitTest/TestData/TileService/UT_BaseMap_Metatiled.tsd (from rev 9509, sandbox/jng/tiling_v2/UnitTest/TestData/TileService/UT_BaseMap_Metatiled.tsd)
===================================================================
--- trunk/MgDev/UnitTest/TestData/TileService/UT_BaseMap_Metatiled.tsd	                        (rev 0)
+++ trunk/MgDev/UnitTest/TestData/TileService/UT_BaseMap_Metatiled.tsd	2019-05-27 12:24:20 UTC (rev 9510)
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TileSetDefinition xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="TileSetDefinition-3.0.0.xsd">
+  <TileStoreParameters>
+    <TileProvider>Default</TileProvider>
+    <Parameter>
+      <Name>TilePath</Name>
+      <Value>../UnitTestFiles/TileCaches/UT_BaseMap_Metatiledf</Value>
+    </Parameter>
+    <Parameter>
+      <Name>TileWidth</Name>
+      <Value>300</Value>
+    </Parameter>
+    <Parameter>
+      <Name>TileHeight</Name>
+      <Value>300</Value>
+    </Parameter>
+    <Parameter>
+      <Name>TileFormat</Name>
+      <Value>PNG</Value>
+    </Parameter>
+    <Parameter>
+      <Name>FiniteScaleList</Name>
+      <Value>200000,100000,50000,25000,12500,6250,3125,1562.5,781.25,390.625</Value>
+    </Parameter>
+    <Parameter>
+      <Name>MetaTileFactor</Name>
+      <Value>4</Value>
+    </Parameter>
+    <Parameter>
+      <Name>CoordinateSystem</Name>
+      <Value>GEOGCS["LL84",DATUM["WGS 84",SPHEROID["WGS 84",6378137,298.25722293287],TOWGS84[0,0,0,0,0,0,0]],PRIMEM["Greenwich",0],UNIT["Degrees",0.01745329252]]</Value>
+    </Parameter>
+  </TileStoreParameters>
+  <Extents>
+    <MinX>-87.79786601383196</MinX>
+    <MaxX>-87.66452777186925</MaxX>
+    <MinY>43.6868578621819</MinY>
+    <MaxY>43.8037962206133</MaxY>
+  </Extents>
+  <BaseMapLayerGroup>
+    <Name>BaseLayers</Name>
+    <Visible>true</Visible>
+    <ShowInLegend>true</ShowInLegend>
+    <ExpandInLegend>true</ExpandInLegend>
+    <LegendLabel>Base Layers</LegendLabel>
+    <BaseMapLayer>
+      <Name>Parcels</Name>
+      <ResourceId>Library://UnitTests/Layers/Parcels.LayerDefinition</ResourceId>
+      <Selectable>true</Selectable>
+      <ShowInLegend>true</ShowInLegend>
+      <LegendLabel>Parcels</LegendLabel>
+      <ExpandInLegend>false</ExpandInLegend>
+    </BaseMapLayer>
+    <BaseMapLayer>
+      <Name>VotingDistricts</Name>
+      <ResourceId>Library://UnitTests/Layers/VotingDistricts.LayerDefinition</ResourceId>
+      <Selectable>true</Selectable>
+      <ShowInLegend>true</ShowInLegend>
+      <LegendLabel>Voting Districts</LegendLabel>
+      <ExpandInLegend>false</ExpandInLegend>
+    </BaseMapLayer>
+  </BaseMapLayerGroup>
+</TileSetDefinition>

Copied: trunk/MgDev/UnitTest/TestData/TileService/UT_BaseMap_OrigSize.tsd (from rev 9509, sandbox/jng/tiling_v2/UnitTest/TestData/TileService/UT_BaseMap_OrigSize.tsd)
===================================================================
--- trunk/MgDev/UnitTest/TestData/TileService/UT_BaseMap_OrigSize.tsd	                        (rev 0)
+++ trunk/MgDev/UnitTest/TestData/TileService/UT_BaseMap_OrigSize.tsd	2019-05-27 12:24:20 UTC (rev 9510)
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TileSetDefinition xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="TileSetDefinition-3.0.0.xsd">
+  <TileStoreParameters>
+    <TileProvider>Default</TileProvider>
+    <Parameter>
+      <Name>TilePath</Name>
+      <Value>%MG_TILE_CACHE_PATH%</Value>
+    </Parameter>
+    <Parameter>
+      <Name>TileWidth</Name>
+      <Value>300</Value>
+    </Parameter>
+    <Parameter>
+      <Name>TileHeight</Name>
+      <Value>300</Value>
+    </Parameter>
+    <Parameter>
+      <Name>TileFormat</Name>
+      <Value>PNG</Value>
+    </Parameter>
+    <Parameter>
+      <Name>FiniteScaleList</Name>
+      <Value>200000,100000,50000,25000,12500,6250,3125,1562.5,781.25,390.625</Value>
+    </Parameter>
+    <Parameter>
+      <Name>CoordinateSystem</Name>
+      <Value>GEOGCS["LL84",DATUM["WGS 84",SPHEROID["WGS 84",6378137,298.25722293287],TOWGS84[0,0,0,0,0,0,0]],PRIMEM["Greenwich",0],UNIT["Degrees",0.01745329252]]</Value>
+    </Parameter>
+  </TileStoreParameters>
+  <Extents>
+    <MinX>-87.79786601383196</MinX>
+    <MaxX>-87.66452777186925</MaxX>
+    <MinY>43.6868578621819</MinY>
+    <MaxY>43.8037962206133</MaxY>
+  </Extents>
+  <BaseMapLayerGroup>
+    <Name>BaseLayers</Name>
+    <Visible>true</Visible>
+    <ShowInLegend>true</ShowInLegend>
+    <ExpandInLegend>true</ExpandInLegend>
+    <LegendLabel>Base Layers</LegendLabel>
+    <BaseMapLayer>
+      <Name>Parcels</Name>
+      <ResourceId>Library://UnitTests/Layers/Parcels.LayerDefinition</ResourceId>
+      <Selectable>true</Selectable>
+      <ShowInLegend>true</ShowInLegend>
+      <LegendLabel>Parcels</LegendLabel>
+      <ExpandInLegend>false</ExpandInLegend>
+    </BaseMapLayer>
+    <BaseMapLayer>
+      <Name>VotingDistricts</Name>
+      <ResourceId>Library://UnitTests/Layers/VotingDistricts.LayerDefinition</ResourceId>
+      <Selectable>true</Selectable>
+      <ShowInLegend>true</ShowInLegend>
+      <LegendLabel>Voting Districts</LegendLabel>
+      <ExpandInLegend>false</ExpandInLegend>
+    </BaseMapLayer>
+  </BaseMapLayerGroup>
+</TileSetDefinition>

Copied: trunk/MgDev/UnitTest/TestData/TileService/UT_XYZ_Metatiled.tsd (from rev 9509, sandbox/jng/tiling_v2/UnitTest/TestData/TileService/UT_XYZ_Metatiled.tsd)
===================================================================
--- trunk/MgDev/UnitTest/TestData/TileService/UT_XYZ_Metatiled.tsd	                        (rev 0)
+++ trunk/MgDev/UnitTest/TestData/TileService/UT_XYZ_Metatiled.tsd	2019-05-27 12:24:20 UTC (rev 9510)
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TileSetDefinition xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="TileSetDefinition-3.0.0.xsd">
+  <TileStoreParameters>
+    <TileProvider>XYZ</TileProvider>
+    <Parameter>
+      <Name>TilePath</Name>
+      <Value>../UnitTestFiles/TileCaches/UT_XYZ_Metatiled</Value>
+    </Parameter>
+    <Parameter>
+      <Name>TileFormat</Name>
+      <Value>PNG</Value>
+    </Parameter>
+    <Parameter>
+      <Name>MetaTileFactor</Name>
+      <Value>4</Value>
+    </Parameter>
+  </TileStoreParameters>
+  <Extents>
+    <MinX>-87.79786601383196</MinX>
+    <MaxX>-87.66452777186925</MaxX>
+    <MinY>43.6868578621819</MinY>
+    <MaxY>43.8037962206133</MaxY>
+  </Extents>
+  <BaseMapLayerGroup>
+    <Name>BaseLayers</Name>
+    <Visible>true</Visible>
+    <ShowInLegend>true</ShowInLegend>
+    <ExpandInLegend>true</ExpandInLegend>
+    <LegendLabel>Base Layers</LegendLabel>
+    <BaseMapLayer>
+      <Name>Parcels</Name>
+      <ResourceId>Library://UnitTests/Layers/Parcels.LayerDefinition</ResourceId>
+      <Selectable>true</Selectable>
+      <ShowInLegend>true</ShowInLegend>
+      <LegendLabel>Parcels</LegendLabel>
+      <ExpandInLegend>false</ExpandInLegend>
+    </BaseMapLayer>
+    <BaseMapLayer>
+      <Name>VotingDistricts</Name>
+      <ResourceId>Library://UnitTests/Layers/VotingDistricts.LayerDefinition</ResourceId>
+      <Selectable>true</Selectable>
+      <ShowInLegend>true</ShowInLegend>
+      <LegendLabel>Voting Districts</LegendLabel>
+      <ExpandInLegend>false</ExpandInLegend>
+    </BaseMapLayer>
+  </BaseMapLayerGroup>
+</TileSetDefinition>

Modified: trunk/MgDev/Web/src/DotNetUnmanagedApi/MapGuideCommon/MapGuideCommonApiGen.xml
===================================================================
--- trunk/MgDev/Web/src/DotNetUnmanagedApi/MapGuideCommon/MapGuideCommonApiGen.xml	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Web/src/DotNetUnmanagedApi/MapGuideCommon/MapGuideCommonApiGen.xml	2019-05-27 12:24:20 UTC (rev 9510)
@@ -148,6 +148,7 @@
     <Header path="../../../../Common/MapGuideCommon/Services/MappingService.h" />
     <Header path="../../../../Common/MapGuideCommon/Services/MapPlot.h" />
     <Header path="../../../../Common/MapGuideCommon/Services/MapPlotCollection.h" />
+    <Header path="../../../../Common/MapGuideCommon/Services/Metatile.h" />
     <Header path="../../../../Common/MapGuideCommon/Services/PackageStatusInformation.h" />
     <Header path="../../../../Common/MapGuideCommon/Services/PlotSpecification.h" />
     <Header path="../../../../Common/MapGuideCommon/Services/ProfilingService.h" />

Modified: trunk/MgDev/Web/src/MapGuideApi/MapGuideApiGen.xml
===================================================================
--- trunk/MgDev/Web/src/MapGuideApi/MapGuideApiGen.xml	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/Web/src/MapGuideApi/MapGuideApiGen.xml	2019-05-27 12:24:20 UTC (rev 9510)
@@ -426,6 +426,7 @@
     <Header path="../../../Common/MapGuideCommon/Services/MappingService.h" />
     <Header path="../../../Common/MapGuideCommon/Services/MapPlot.h" />
     <Header path="../../../Common/MapGuideCommon/Services/MapPlotCollection.h" />
+    <Header path="../../../Common/MapGuideCommon/Services/Metatile.h" />
     <Header path="../../../Common/MapGuideCommon/Services/PackageStatusInformation.h" />
     <Header path="../../../Common/MapGuideCommon/Services/PlotSpecification.h" />
     <Header path="../../../Common/MapGuideCommon/Services/ProfilingService.h" />

Modified: trunk/MgDev/cmake_linuxapt.sh
===================================================================
--- trunk/MgDev/cmake_linuxapt.sh	2019-05-27 12:07:18 UTC (rev 9509)
+++ trunk/MgDev/cmake_linuxapt.sh	2019-05-27 12:24:20 UTC (rev 9510)
@@ -62,6 +62,7 @@
 check_build()
 {
     error=$?
+    echo "Error code is currently: $error"
     if [ $error -ne 0 ]; then
         echo "$LIB_NAME: Error build failed ($error)................."
         # Return back to this dir before bailing



More information about the mapguide-commits mailing list