[mapguide-commits] r9630 - in trunk/MgDev: . Common/CoordinateSystem Common/Foundation/Data Common/Geometry Common/Geometry/Spatial Common/MapGuideCommon/Services Common/Renderers Common/Stylization Doc/samples Doc/samples/ol2samples Doc/samples/ol2samples/wms Doc/samples/ol2samples/xyz Server/src/Services/Mapping Server/src/Services/Rendering Server/src/Services/Tile Server/src/UnitTesting UnitTest/TestData/Samples/Sheboygan UnitTest/TestData/Samples/Sheboygan/Library/Samples/Sheboygan/TileSets

svn_mapguide at osgeo.org svn_mapguide at osgeo.org
Sun Oct 6 05:18:03 PDT 2019


Author: jng
Date: 2019-10-06 05:18:03 -0700 (Sun, 06 Oct 2019)
New Revision: 9630

Added:
   trunk/MgDev/Common/Geometry/Spatial/SpatialUtilities.h
   trunk/MgDev/Common/Renderers/MVTRenderer.cpp
   trunk/MgDev/Common/Renderers/MVTRenderer.h
   trunk/MgDev/Common/Renderers/mvt/
   trunk/MgDev/Doc/samples/ol2samples/mvt/
   trunk/MgDev/Server/src/Services/Rendering/OpRenderTileMVT.cpp
   trunk/MgDev/Server/src/Services/Rendering/OpRenderTileMVT.h
   trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/Library/Samples/Sheboygan/TileSets/SheboyganMVT.TileSetDefinition_CONTENT.xml
   trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/Library/Samples/Sheboygan/TileSets/SheboyganMVT.TileSetDefinition_HEADER.xml
Removed:
   trunk/MgDev/Common/Geometry/Spatial/SpatialUtility.h
Modified:
   trunk/MgDev/
   trunk/MgDev/CMakeLists.txt
   trunk/MgDev/Common/CoordinateSystem/CoordSysGrids.cpp
   trunk/MgDev/Common/CoordinateSystem/CoordSysMgrsZone.cpp
   trunk/MgDev/Common/Foundation/Data/ByteSink.cpp
   trunk/MgDev/Common/Foundation/Data/MimeType.cpp
   trunk/MgDev/Common/Foundation/Data/MimeType.h
   trunk/MgDev/Common/Geometry/ArcSegment.cpp
   trunk/MgDev/Common/Geometry/GeosUtil.cpp
   trunk/MgDev/Common/Geometry/Spatial/SpatialUtility.cpp
   trunk/MgDev/Common/Geometry/Spatial/SpatialUtilityCircularArc.cpp
   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/Renderers/CMakeLists.txt
   trunk/MgDev/Common/Renderers/Renderers.vcxproj
   trunk/MgDev/Common/Renderers/Renderers.vcxproj.filters
   trunk/MgDev/Common/Stylization/DefaultStylizer.cpp
   trunk/MgDev/Common/Stylization/LineBuffer.cpp
   trunk/MgDev/Common/Stylization/RS_FeatureReader.h
   trunk/MgDev/Common/Stylization/Renderer.h
   trunk/MgDev/Common/Stylization/RendererStyles.h
   trunk/MgDev/Common/Stylization/SE_Renderer.cpp
   trunk/MgDev/Common/Stylization/SE_Renderer.h
   trunk/MgDev/Common/Stylization/StylizationEngine.cpp
   trunk/MgDev/Doc/samples/ol2samples/wms/index.html
   trunk/MgDev/Doc/samples/ol2samples/xyz/index_ol.html
   trunk/MgDev/Doc/samples/samples.php
   trunk/MgDev/License.txt
   trunk/MgDev/Server/src/Services/Mapping/RSMgFeatureReader.cpp
   trunk/MgDev/Server/src/Services/Mapping/RSMgFeatureReader.h
   trunk/MgDev/Server/src/Services/Mapping/ServerMappingService.vcxproj
   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/TileCacheXYZProvider.cpp
   trunk/MgDev/Server/src/UnitTesting/CMakeLists.txt
   trunk/MgDev/Server/src/UnitTesting/TestMisc.cpp
   trunk/MgDev/Server/src/UnitTesting/TestMisc.h
   trunk/MgDev/Server/src/UnitTesting/TestRenderingService.cpp
   trunk/MgDev/Server/src/UnitTesting/TestRenderingService.h
   trunk/MgDev/Server/src/UnitTesting/UnitTesting.vcxproj
   trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/Library/Samples/Sheboygan/TileSets/SheboyganUTFGrid.TileSetDefinition_CONTENT.xml
   trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/MgResourcePackageManifest.xml
Log:
Implement MapGuide RFC 177

Merged revision(s) 9604-9629 from sandbox/jng/mvt_alt:

Index: trunk/MgDev
===================================================================
--- trunk/MgDev	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev	2019-10-06 12:18:03 UTC (rev 9630)

Property changes on: trunk/MgDev
___________________________________________________________________
Modified: svn:mergeinfo
## -19,6 +19,8 ##
 /sandbox/jng/fusion_entrypoint_refactor:9400-9406
 /sandbox/jng/geoprocessing:9205-9229
 /sandbox/jng/geos34x:8256-8259
+/sandbox/jng/mvt:9527-9528,9535,9537,9546-9547
+/sandbox/jng/mvt_alt:9604-9629
 /sandbox/jng/ogc:9240-9255
 /sandbox/jng/php56x:8975-8985
 /sandbox/jng/querymapfeatures_selectionkey:9590-9591
## -32,5 +34,4 ##
 /sandbox/jng/v30:8212-8227
 /sandbox/jng/v4:9511-9519
 /sandbox/jng/wfs_hits:9569-9577
-/sandbox/rfc94:5099-5163
-/trunk/MgDev:9397-9399
\ No newline at end of property
+/sandbox/rfc94:5099-5163
\ No newline at end of property
Modified: trunk/MgDev/CMakeLists.txt
===================================================================
--- trunk/MgDev/CMakeLists.txt	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/CMakeLists.txt	2019-10-06 12:18:03 UTC (rev 9630)
@@ -62,13 +62,13 @@
 
 if (NOT MG_VERSION_MAJOR)
     set(MG_VERSION_MAJOR 4)
-endif (MG_VERSION_MAJOR)
+endif (NOT MG_VERSION_MAJOR)
 if (NOT MG_VERSION_MINOR)
     set(MG_VERSION_MINOR 0)
-endif (MG_VERSION_MINOR)
+endif (NOT MG_VERSION_MINOR)
 if (NOT MG_VERSION_RELEASE)
     set(MG_VERSION_RELEASE 0)
-endif (MG_VERSION_RELEASE)
+endif (NOT MG_VERSION_RELEASE)
 if (NOT MG_VERSION_REV)
     set(MG_VERSION_REV 0)
 endif (NOT MG_VERSION_REV)

Modified: trunk/MgDev/Common/CoordinateSystem/CoordSysGrids.cpp
===================================================================
--- trunk/MgDev/Common/CoordinateSystem/CoordSysGrids.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/CoordinateSystem/CoordSysGrids.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -20,7 +20,7 @@
 #include "CoordSysUtil.h"
 #include "CriticalSection.h"
 #include "Spatial/MathUtility.h"
-#include "Spatial/SpatialUtility.h"
+#include "Spatial/SpatialUtilities.h"
 
 #include "CoordSys.h"
 #include "CoordSysGrids.h"

Modified: trunk/MgDev/Common/CoordinateSystem/CoordSysMgrsZone.cpp
===================================================================
--- trunk/MgDev/Common/CoordinateSystem/CoordSysMgrsZone.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/CoordinateSystem/CoordSysMgrsZone.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -21,7 +21,7 @@
 #include "CriticalSection.h"    //for SmartCriticalClass
 
 #include "Spatial/MathUtility.h"
-#include "Spatial/SpatialUtility.h"
+#include "Spatial/SpatialUtilities.h"
 #include "CoordSysGrids.h"
 #include "CoordSysOneGrid.h"
 #include "CoordSysMgrsZone.h"

Modified: trunk/MgDev/Common/Foundation/Data/ByteSink.cpp
===================================================================
--- trunk/MgDev/Common/Foundation/Data/ByteSink.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Foundation/Data/ByteSink.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -80,6 +80,7 @@
 
     if (!mimeType.empty()
         && mimeType.find(L"text") == STRING::npos
+        && MgMimeType::Mvt != mimeType
         && MgMimeType::Binary != mimeType)
     {
         // Note that the mime type of resource data is determined by the

Modified: trunk/MgDev/Common/Foundation/Data/MimeType.cpp
===================================================================
--- trunk/MgDev/Common/Foundation/Data/MimeType.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Foundation/Data/MimeType.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -37,3 +37,4 @@
 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";
+const STRING MgMimeType::Mvt             = L"application/vnd.mapbox-vector-tile";
\ No newline at end of file

Modified: trunk/MgDev/Common/Foundation/Data/MimeType.h
===================================================================
--- trunk/MgDev/Common/Foundation/Data/MimeType.h	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Foundation/Data/MimeType.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -87,6 +87,10 @@
     /// \brief
     /// application/metatile
     static const STRING Meta;      ///\if INTERNAL value("application/metatile") \endif
+
+    /// \brief
+    /// application/vnd.mapbox-vector-tile
+    static const STRING Mvt;       ///\if INTERNAL value("application/vnd.mapbox-vector-tile") \endif
 };
 /// \}
 

Modified: trunk/MgDev/Common/Geometry/ArcSegment.cpp
===================================================================
--- trunk/MgDev/Common/Geometry/ArcSegment.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Geometry/ArcSegment.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -16,7 +16,7 @@
 //
 
 #include "GeometryCommon.h"
-#include "Spatial/SpatialUtility.h"
+#include "Spatial/SpatialUtilities.h"
 
 //////////////////////////////////////////////////////////////////
 // Construct a MgArcSegment object

Modified: trunk/MgDev/Common/Geometry/GeosUtil.cpp
===================================================================
--- trunk/MgDev/Common/Geometry/GeosUtil.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Geometry/GeosUtil.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -25,7 +25,7 @@
 #include "Buffer/buffer.h"
 #include "GeosUtil.h"
 #include "GeometryExceptionDef.h"
-#include "Spatial/SpatialUtility.h"
+#include "Spatial/SpatialUtilities.h"
 #include "GeometricEntityType.h"
 
 using namespace geos;

Copied: trunk/MgDev/Common/Geometry/Spatial/SpatialUtilities.h (from rev 9629, sandbox/jng/mvt_alt/Common/Geometry/Spatial/SpatialUtilities.h)
===================================================================
--- trunk/MgDev/Common/Geometry/Spatial/SpatialUtilities.h	                        (rev 0)
+++ trunk/MgDev/Common/Geometry/Spatial/SpatialUtilities.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -0,0 +1,380 @@
+//
+//  Copyright (C) 2004-2011 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 MgSpatialUtilities_H
+#define MgSpatialUtilities_H
+
+// NOTE: This header has been renamed to avoid collison with FDO's spatial utility header
+// of the same name and relative path!
+
+/// \cond INTERNAL
+
+/// \brief
+/// Spatial utility class.
+class MgSpatialUtility
+{
+public:
+
+    /// Converts arcs to linestrings
+    static MgGeometry* TesselateCurve(MgGeometry* curve);
+    /// Converts arcs to linestrings
+    static MgGeometryComponent* TesselateGeometryComponent(MgGeometryComponent* curve);
+
+    /// \brief
+    /// Approximates a geometry, which may contain parametric curve components,
+    /// with a geometry that only contains straight, linear components.
+    ///
+    /// \remarks
+    /// If the given geometry does not require approximation, this method returns
+    /// the input geometry, and thus avoids unnecessary object creation.
+    /// Tolerances are in same units as the geometry's ordinates.
+    /// If the given tolerances are both zero, a tesselated geometry is produced,
+    /// wherein arcs are represented using only their three defining positions.
+    /// Math performed is all in implicit Cartesian (rectilinear) coordinate system.
+    /// Measurement ("M") ordinates of interpolated positions undergo linear
+    /// interpolation from an arc's start position to end position (possibly disregarding
+    /// an "M" ordinate in the arc's mid position).
+    /// Unless the arc is too short for any interpolation, the "mid" point parameter
+    /// used to define arcs is not guaranteed to appear among interpolated points.
+    ///
+    /// \param geometry
+    /// Input Geometry to approximate
+    /// \param maxSpacing
+    /// Input Maximum length of each line segment used to approximate parametric curves. Ignored if zero.
+    /// \param maxOffset
+    /// Input Maximum distance that a line segment may be from the curve that it approximates (error tolerance). Ignored if zero.
+    /// \param geometryFactory
+    /// Input Geometry factory for the creation of the approximated geometry.
+    ///
+    /// \return
+    /// Returns An approximation of the given geometry.
+    ///
+    static MgGeometry * ApproximateGeometryWithLineStrings(
+        MgGeometry * geometry,
+        double maxSpacing,
+        double maxOffset,
+        MgGeometryFactory* geometryFactory);
+
+    static bool ArePositionsEqualXYZ(
+        MgCoordinate * p1,
+        MgCoordinate * p2);
+
+    static bool ArePositionsEqualXY(
+        MgCoordinate * p1,
+        MgCoordinate * p2);
+
+    static double DistanceBetweenPositionsXYZ(
+        MgCoordinate * p1,
+        MgCoordinate * p2);
+
+    static double DistanceBetweenPositionsXY(
+        MgCoordinate * p1,
+        MgCoordinate * p2);
+
+    /// Unit normal from plane defined by vectors p1p2 to p2p3 using right-hand rule.
+    /// This will return (0,0,0) for parallel vectors (collinear points) or zero-length
+    /// vectors.
+    static MgCoordinate * UnitNormalVector(
+        MgCoordinate * p1,
+        MgCoordinate * p2,
+        MgCoordinate * p3);
+
+    static void AppendPositionToDistinctCollection(
+        MgCoordinateCollection * distinctPositions,
+        MgCoordinate * positionToAppend);
+
+    /// Return a new point with the location of the given point after rotation
+    /// about the given axis and center.
+    /// This method assumes that the involved data has XYZ dimensionality.
+    /// <p><b>Note:</b> "axisVector" must be a unit normal to the plane of rotation.
+    static MgCoordinate * RotatePosition(
+        MgCoordinate * position,
+        double angle,
+        MgCoordinate * axisVector,
+        MgCoordinate * center,
+        bool includeM,
+        double m);
+
+    static MgLinearRing* CurveRingToLinearRing(MgCurveRing* curveRing, double maxSpacing, double maxOffset);
+    static MgLinearSegment* ArcSegmentToLinearSegment(MgArcSegment* segment, double maxSpacing, double maxOffset);
+
+    ///////////////////////////////////////////////////////////////////////////
+    /// <summary>
+    /// Extracts the greatest magnitude of the all the coordinates in a
+    /// coordinate collection.  Return value is always positive.
+    /// </summary>
+    /// <param name="coordinteIterator">
+    /// An iterator dervied from the target coordinate collection.
+    /// </param>
+    /// <returns>
+    /// Returns a positive value which is the largest magnitude of all
+    /// ordinates in the collection.
+    /// </returns>
+    /// <remarks>
+    /// Intended to be used to determine a suitable tolerance value for such
+    /// things as coordinate comparisons.
+    /// </remarks>
+    static double GreatestMagnitude (MgCoordinateIterator* coordinateIterator);
+
+    ///////////////////////////////////////////////////////////////////////////
+    /// <summary>
+    /// Computes the intersection of two 2D line segments.  Intersection point,
+    /// if any, is returned in the provided result coordinate which (of course)
+    /// must already exist.  All point arguments are expected (not required)
+    /// to be MgCoordinateXY objects.
+    /// </summary>
+    /// <param name="result">
+    /// The calculated intersection point is returned in this variable which,
+    /// of course, must exist.  Coordinates of <c>result</c> remain unaltered
+    /// if an intersection does not exist.  Only X and Y are returned.
+    /// </param>
+    /// <param name="seg1From">
+    /// The initial point of the first line segment.
+    /// </param>
+    /// <param name="seg1To">
+    /// The end point of the first line segment.
+    /// </param>
+    /// <param name="seg2From">
+    /// The initial point of the second line segment.
+    /// </param>
+    /// <param name="seg2To">
+    /// The end point of the second line segment.
+    /// </param>
+    /// <param name="magnitude">
+    /// A value which represents the greatest coordinate value in the dataset
+    /// being processed.  Used to calculate an appropriate "fuzzy" value for
+    /// coordinate comparisions, etc.
+    /// </param>
+    /// <returns>
+    /// Return status of the intersection of the two segments as a bit map of
+    /// values as defined in <c>SpatialUtilityStatus.h</c>
+    /// </returns>
+    /// <remarks>
+    /// In determining if the intersection point resides on a line, an intersection
+    /// point identical to the 'to' point is considered on the line, but an
+    /// intersection point identical to the 'from' point is _NOT_ considered to be
+    /// on the line.  Such a convention is necessary to avoid the appearance
+    /// of two intersections when indeed there is only one when processing a
+    /// a line string, for example.
+    /// </remarks>
+    /// <exception cref="MgNullReferenceException">
+    /// Thrown if any argument is null
+    /// </exception>
+    static INT32 SegmentIntersection (MgCoordinate* result,MgCoordinate* seg1From,
+                                                           MgCoordinate* seg1To,
+                                                           MgCoordinate* seg2From,
+                                                           MgCoordinate* seg2To,
+                                                           double magnitude = 1.0E+10);
+
+    ///////////////////////////////////////////////////////////////////////////////
+    /// <summary>
+    /// Returns a collection of coordinates with all of the intersection points of
+    /// the provided segment with the polygon provided by the first argument.
+    /// </summary>
+    /// <param name="polyItr">
+    /// An iterator of the closed ring to which the provided segment is
+    /// intersected.
+    /// </param>
+    /// <param name="segFrom">
+    /// The initial point of the line segment to be intersected with the provided
+    /// closed ring.
+    /// </param>
+    /// <param name="segTo">
+    /// The ending point of the line segment to be intersected with the provided
+    /// closed ring.
+    /// </param>
+    /// <returns>
+    /// A new collection of <c>MgCoordinateXYM</c> points, one such point for each
+    /// intersection is returned.  This collection can be, and often is, an empty
+    /// collection.  A null pointer is never returned.<para>
+    /// The X and Y ordinates of each point in the returned collection represent
+    /// the actual location of the intersection.  The M ordinate will carry, as
+    /// a double, the status of the intersection as returned by the
+    /// <c>SegmentIntersection</c> function.
+    /// </returns>
+    static MgCoordinateCollection* PolySegIntersection (MgCoordinateIterator* polyItr,
+                                                        MgCoordinate* segFrom,
+                                                        MgCoordinate* segTo);
+
+    ///////////////////////////////////////////////////////////////////////////////
+    /// <summary>
+    /// Adds a coordinate to a coordinate collection such that the resulting
+    /// collection represents a collection of points in sequence from the
+    /// provided base point.
+    /// </summary>
+    /// <param name="collection">
+    /// The coordinate collection to which the <c>newPoint</c> is to be added.
+    /// </param>
+    /// <param name="newPoint">
+    /// The point to be added to the provided collection.
+    /// </param>
+    /// <param name="basePoint">
+    /// The base point which is used to order the collection.
+    /// </param>
+    /// <returns>
+    /// the index at which the insertion occurred.
+    /// </returns>
+    /// <remarks>
+    /// The purpose of this function is serve as a helper for the PolySegIntersection
+    /// function.  It enables PolySegIntersection to return a point collection such
+    /// that the points in the collection present an orderly sequence of points from
+    /// the provided base point.  Thus, if the original line segment provided to
+    /// PolySegIntersection proceeded in the south to north direction, the point
+    /// collection returned would also be returned in that order; regardless of the
+    /// shape of the polygon or the direction in which it proceeds.<para>
+    /// The determination as to the insertion point of <c>newPoint</c> is based solely
+    /// on the X and Y ordinates of <c>newPoint</c> and the points which already
+    /// exist in the provided collection.  The type of <c>newPoint</c> and the points
+    /// in the collection is immaterial.
+    /// </remarks>
+    static INT32 AddToCoordinateCollection (MgCoordinateCollection* collection,MgCoordinate* newPoint,
+                                                                               MgCoordinate* basePoint);
+
+    ///////////////////////////////////////////////////////////////////////////////
+    /// <summary>
+    /// Determines if the provided point is inside (or actually on) the closed
+    /// ring provided by the <c>polyItr</c> argument.
+    /// </summary>
+    /// <param name="polyItr">
+    /// An iterator of the closed ring which is the subject polygon.
+    /// </param>
+    /// <param name="queryPoint">
+    /// The 2D point whose status is to be determined.
+    /// </param>
+    /// <returns>
+    /// Returns zero if the query point is outside the closed ring, +1 if the
+    /// query point is precisely on the closed ring (per MgMathUtility::DblCmp),
+    /// or +2 if the query point is inside the closed ring.
+    /// </returns>
+    /// <remarks>
+    /// Currently, this function calculates the envelope of the provided closed
+    /// ring in order to determine a point which is known to be outside of the
+    /// closed ring.  An overloaded function which accepts a point known to be
+    /// outside the closed ring which is a lot faster is also available.
+    /// </remarks>
+    static INT32 PointIsInPolygon (MgCoordinateIterator* polyItr,MgCoordinate* queryPoint);
+
+    ///////////////////////////////////////////////////////////////////////////////
+    /// <summary>
+    /// Determines a point guaranteed to be ouside the provided polygon.
+    /// </summary>
+    /// <param name="polyItr">
+    /// An iterator of the closed ring which is the subject polygon.
+    /// </param>
+    /// <returns>
+    /// Returns an <c>MgCoordinateXY</c> point which is guaranteed to be ooutside
+    /// the provided polygon.
+    /// </returns>
+    static MgCoordinate* PointOutsidePolygon (MgCoordinateIterator* polyItr);
+
+    ///////////////////////////////////////////////////////////////////////////////
+    /// <summary>
+    /// Determines if the provided point is inside (or actually on) the closed
+    /// ring provided by the <c>polyItr</c> argument.
+    /// </summary>
+    /// <param name="polyItr">
+    /// An iterator of the closed ring which is the subject polygon.
+    /// </param>
+    /// <param name="outsidePoint">
+    /// A 2D point which is known to be outside of the provided polygon.  Can
+    /// be obtained from <c>PointOutsidePolygon</c>.
+    /// <param name="queryPoint">
+    /// The 2D point whose status is to be determined.
+    /// </param>
+    /// <returns>
+    /// Returns zero if the query point is outside the closed ring, +1 if the
+    /// query point is precisely on the closed ring (per MgMathUtility::DblCmp),
+    /// or +2 if the query point is inside the closed ring.
+    /// </returns>
+    /// <remarks>
+    /// This function requires that the calling module supply a point known to
+    /// be outside the polygon.  Thus, higher performance can be obtained when
+    /// multiple queries against the same polygon are required.
+    /// </remarks>
+    static INT32 PointIsInPolygon (MgCoordinateIterator* polyItr,MgCoordinate* outsidePoint,
+                                                                 MgCoordinate* queryPoint);
+
+    ///////////////////////////////////////////////////////////////////////////////
+    /// <summary>
+    /// Clips a line string to an arbitrary polygon returning a collection of line
+    /// strings which represent the portions of the provided line string which are
+    /// inside the provided closed ring.
+    /// </summary>
+    /// <param name="polyItr">
+    /// An iterator to the closed ring to which the provided line string is to be
+    /// clipped.
+    /// </param>
+    /// <param name="lineItr">
+    /// An iterator for the line string which is to be clipped.
+    /// </param>
+    /// <param name="includeCollinear">
+    /// A true value indicates that segments, and/or portions of segments,
+    /// of the line string to be clipped which are collinear with the clipping
+    /// boundary are to be considered to be inside the clipping boundary.  A
+    /// false value causes collinear segments (or portions thereof) to be
+    /// outside the clipping boundary and thus excluded from the returned
+    /// line string collection.
+    /// </param>
+    /// <returns>
+    /// A collection of line string objects which represents the portions of the
+    /// provided line string which are inside of the provided closed ring.  This
+    /// collection may be empty; a null pointer is never returned.
+    /// </returns>
+    static MgLineStringCollection* ClipStringToPolygon (MgCoordinateIterator* polyItr,
+                                                        MgCoordinateIterator* lineItr,
+                                                        bool includeCollinear = false);
+protected:
+
+    MgSpatialUtility() {};
+    ~MgSpatialUtility() {};
+
+    static void AppendLinearizedCurveSegmentsToDistinctCollection(
+        MgCoordinateCollection * distinctPositions,
+        MgCurveSegmentCollection * curveSegments,
+        double maxSpacing,
+        double maxOffset);
+
+    static void AppendPositionsToDistinctCollection(
+        MgCoordinateCollection * distinctPositions,
+        MgCoordinateCollection * positionsToAppend);
+
+    // Values which are stored in the M member of the internally used point
+    // collection; i.e. the status of each point relative to the clip ploygon.
+    static const INT32 StringPointNotDetermined = 0;
+    static const INT32 StringPointIsOutside     = 1;
+    static const INT32 StringPointIsOnBoundary  = 2;
+    static const INT32 StringPointIsInside      = 3;
+
+    // Values which are stored in the Z member of the internally used point
+    // collection; i.e. the status of each segment relative to the clip
+    // ploygon.  The value stored refers to the segment which starts at
+    // the point in which the segment status is stored.
+    static const INT32 StringSegNotDetermined = 0;
+    static const INT32 StringSegIsOutside     = 1;
+    static const INT32 StringSegIsCollinear   = 2;
+    static const INT32 StringSegIsInside      = 3;
+
+    static MgCoordinateCollection* StringClipPhaseOne (MgCoordinateIterator* lineString,
+                                                       MgCoordinateIterator* itrPolygon);
+    static void StringClipPhaseTwo (MgCoordinateIterator* lineString,MgCoordinateIterator* itrPolygon);
+    static void StringClipPhaseThree (MgCoordinateIterator* lineString,MgCoordinateIterator* itrPolygon);
+    static MgLineStringCollection* StringClipPhaseFour (MgCoordinateIterator* lineString,bool collinear = false,
+                                                                                         bool outside = false);
+};
+/// \endcond
+
+#endif

Modified: trunk/MgDev/Common/Geometry/Spatial/SpatialUtility.cpp
===================================================================
--- trunk/MgDev/Common/Geometry/Spatial/SpatialUtility.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Geometry/Spatial/SpatialUtility.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -19,7 +19,7 @@
 #include "GeometryCommon.h"
 #include "MathUtility.h"
 #include "SpatialUtilityCircularArc.h"
-#include "SpatialUtility.h"
+#include "SpatialUtilities.h"
 #include "SpatialUtilityStatus.h"
 
 #define EPSILON 1e-10

Deleted: trunk/MgDev/Common/Geometry/Spatial/SpatialUtility.h
===================================================================
--- trunk/MgDev/Common/Geometry/Spatial/SpatialUtility.h	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Geometry/Spatial/SpatialUtility.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -1,377 +0,0 @@
-//
-//  Copyright (C) 2004-2011 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 MgSpatialUtility_H
-#define MgSpatialUtility_H
-
-/// \cond INTERNAL
-
-/// \brief
-/// Spatial utility class.
-class MgSpatialUtility
-{
-public:
-
-    /// Converts arcs to linestrings
-    static MgGeometry* TesselateCurve(MgGeometry* curve);
-    /// Converts arcs to linestrings
-    static MgGeometryComponent* TesselateGeometryComponent(MgGeometryComponent* curve);
-
-    /// \brief
-    /// Approximates a geometry, which may contain parametric curve components,
-    /// with a geometry that only contains straight, linear components.
-    ///
-    /// \remarks
-    /// If the given geometry does not require approximation, this method returns
-    /// the input geometry, and thus avoids unnecessary object creation.
-    /// Tolerances are in same units as the geometry's ordinates.
-    /// If the given tolerances are both zero, a tesselated geometry is produced,
-    /// wherein arcs are represented using only their three defining positions.
-    /// Math performed is all in implicit Cartesian (rectilinear) coordinate system.
-    /// Measurement ("M") ordinates of interpolated positions undergo linear
-    /// interpolation from an arc's start position to end position (possibly disregarding
-    /// an "M" ordinate in the arc's mid position).
-    /// Unless the arc is too short for any interpolation, the "mid" point parameter
-    /// used to define arcs is not guaranteed to appear among interpolated points.
-    ///
-    /// \param geometry
-    /// Input Geometry to approximate
-    /// \param maxSpacing
-    /// Input Maximum length of each line segment used to approximate parametric curves. Ignored if zero.
-    /// \param maxOffset
-    /// Input Maximum distance that a line segment may be from the curve that it approximates (error tolerance). Ignored if zero.
-    /// \param geometryFactory
-    /// Input Geometry factory for the creation of the approximated geometry.
-    ///
-    /// \return
-    /// Returns An approximation of the given geometry.
-    ///
-    static MgGeometry * ApproximateGeometryWithLineStrings(
-        MgGeometry * geometry,
-        double maxSpacing,
-        double maxOffset,
-        MgGeometryFactory* geometryFactory);
-
-    static bool ArePositionsEqualXYZ(
-        MgCoordinate * p1,
-        MgCoordinate * p2);
-
-    static bool ArePositionsEqualXY(
-        MgCoordinate * p1,
-        MgCoordinate * p2);
-
-    static double DistanceBetweenPositionsXYZ(
-        MgCoordinate * p1,
-        MgCoordinate * p2);
-
-    static double DistanceBetweenPositionsXY(
-        MgCoordinate * p1,
-        MgCoordinate * p2);
-
-    /// Unit normal from plane defined by vectors p1p2 to p2p3 using right-hand rule.
-    /// This will return (0,0,0) for parallel vectors (collinear points) or zero-length
-    /// vectors.
-    static MgCoordinate * UnitNormalVector(
-        MgCoordinate * p1,
-        MgCoordinate * p2,
-        MgCoordinate * p3);
-
-    static void AppendPositionToDistinctCollection(
-        MgCoordinateCollection * distinctPositions,
-        MgCoordinate * positionToAppend);
-
-    /// Return a new point with the location of the given point after rotation
-    /// about the given axis and center.
-    /// This method assumes that the involved data has XYZ dimensionality.
-    /// <p><b>Note:</b> "axisVector" must be a unit normal to the plane of rotation.
-    static MgCoordinate * RotatePosition(
-        MgCoordinate * position,
-        double angle,
-        MgCoordinate * axisVector,
-        MgCoordinate * center,
-        bool includeM,
-        double m);
-
-    static MgLinearRing* CurveRingToLinearRing(MgCurveRing* curveRing, double maxSpacing, double maxOffset);
-    static MgLinearSegment* ArcSegmentToLinearSegment(MgArcSegment* segment, double maxSpacing, double maxOffset);
-
-    ///////////////////////////////////////////////////////////////////////////
-    /// <summary>
-    /// Extracts the greatest magnitude of the all the coordinates in a
-    /// coordinate collection.  Return value is always positive.
-    /// </summary>
-    /// <param name="coordinteIterator">
-    /// An iterator dervied from the target coordinate collection.
-    /// </param>
-    /// <returns>
-    /// Returns a positive value which is the largest magnitude of all
-    /// ordinates in the collection.
-    /// </returns>
-    /// <remarks>
-    /// Intended to be used to determine a suitable tolerance value for such
-    /// things as coordinate comparisons.
-    /// </remarks>
-    static double GreatestMagnitude (MgCoordinateIterator* coordinateIterator);
-
-    ///////////////////////////////////////////////////////////////////////////
-    /// <summary>
-    /// Computes the intersection of two 2D line segments.  Intersection point,
-    /// if any, is returned in the provided result coordinate which (of course)
-    /// must already exist.  All point arguments are expected (not required)
-    /// to be MgCoordinateXY objects.
-    /// </summary>
-    /// <param name="result">
-    /// The calculated intersection point is returned in this variable which,
-    /// of course, must exist.  Coordinates of <c>result</c> remain unaltered
-    /// if an intersection does not exist.  Only X and Y are returned.
-    /// </param>
-    /// <param name="seg1From">
-    /// The initial point of the first line segment.
-    /// </param>
-    /// <param name="seg1To">
-    /// The end point of the first line segment.
-    /// </param>
-    /// <param name="seg2From">
-    /// The initial point of the second line segment.
-    /// </param>
-    /// <param name="seg2To">
-    /// The end point of the second line segment.
-    /// </param>
-    /// <param name="magnitude">
-    /// A value which represents the greatest coordinate value in the dataset
-    /// being processed.  Used to calculate an appropriate "fuzzy" value for
-    /// coordinate comparisions, etc.
-    /// </param>
-    /// <returns>
-    /// Return status of the intersection of the two segments as a bit map of
-    /// values as defined in <c>SpatialUtilityStatus.h</c>
-    /// </returns>
-    /// <remarks>
-    /// In determining if the intersection point resides on a line, an intersection
-    /// point identical to the 'to' point is considered on the line, but an
-    /// intersection point identical to the 'from' point is _NOT_ considered to be
-    /// on the line.  Such a convention is necessary to avoid the appearance
-    /// of two intersections when indeed there is only one when processing a
-    /// a line string, for example.
-    /// </remarks>
-    /// <exception cref="MgNullReferenceException">
-    /// Thrown if any argument is null
-    /// </exception>
-    static INT32 SegmentIntersection (MgCoordinate* result,MgCoordinate* seg1From,
-                                                           MgCoordinate* seg1To,
-                                                           MgCoordinate* seg2From,
-                                                           MgCoordinate* seg2To,
-                                                           double magnitude = 1.0E+10);
-
-    ///////////////////////////////////////////////////////////////////////////////
-    /// <summary>
-    /// Returns a collection of coordinates with all of the intersection points of
-    /// the provided segment with the polygon provided by the first argument.
-    /// </summary>
-    /// <param name="polyItr">
-    /// An iterator of the closed ring to which the provided segment is
-    /// intersected.
-    /// </param>
-    /// <param name="segFrom">
-    /// The initial point of the line segment to be intersected with the provided
-    /// closed ring.
-    /// </param>
-    /// <param name="segTo">
-    /// The ending point of the line segment to be intersected with the provided
-    /// closed ring.
-    /// </param>
-    /// <returns>
-    /// A new collection of <c>MgCoordinateXYM</c> points, one such point for each
-    /// intersection is returned.  This collection can be, and often is, an empty
-    /// collection.  A null pointer is never returned.<para>
-    /// The X and Y ordinates of each point in the returned collection represent
-    /// the actual location of the intersection.  The M ordinate will carry, as
-    /// a double, the status of the intersection as returned by the
-    /// <c>SegmentIntersection</c> function.
-    /// </returns>
-    static MgCoordinateCollection* PolySegIntersection (MgCoordinateIterator* polyItr,
-                                                        MgCoordinate* segFrom,
-                                                        MgCoordinate* segTo);
-
-    ///////////////////////////////////////////////////////////////////////////////
-    /// <summary>
-    /// Adds a coordinate to a coordinate collection such that the resulting
-    /// collection represents a collection of points in sequence from the
-    /// provided base point.
-    /// </summary>
-    /// <param name="collection">
-    /// The coordinate collection to which the <c>newPoint</c> is to be added.
-    /// </param>
-    /// <param name="newPoint">
-    /// The point to be added to the provided collection.
-    /// </param>
-    /// <param name="basePoint">
-    /// The base point which is used to order the collection.
-    /// </param>
-    /// <returns>
-    /// the index at which the insertion occurred.
-    /// </returns>
-    /// <remarks>
-    /// The purpose of this function is serve as a helper for the PolySegIntersection
-    /// function.  It enables PolySegIntersection to return a point collection such
-    /// that the points in the collection present an orderly sequence of points from
-    /// the provided base point.  Thus, if the original line segment provided to
-    /// PolySegIntersection proceeded in the south to north direction, the point
-    /// collection returned would also be returned in that order; regardless of the
-    /// shape of the polygon or the direction in which it proceeds.<para>
-    /// The determination as to the insertion point of <c>newPoint</c> is based solely
-    /// on the X and Y ordinates of <c>newPoint</c> and the points which already
-    /// exist in the provided collection.  The type of <c>newPoint</c> and the points
-    /// in the collection is immaterial.
-    /// </remarks>
-    static INT32 AddToCoordinateCollection (MgCoordinateCollection* collection,MgCoordinate* newPoint,
-                                                                               MgCoordinate* basePoint);
-
-    ///////////////////////////////////////////////////////////////////////////////
-    /// <summary>
-    /// Determines if the provided point is inside (or actually on) the closed
-    /// ring provided by the <c>polyItr</c> argument.
-    /// </summary>
-    /// <param name="polyItr">
-    /// An iterator of the closed ring which is the subject polygon.
-    /// </param>
-    /// <param name="queryPoint">
-    /// The 2D point whose status is to be determined.
-    /// </param>
-    /// <returns>
-    /// Returns zero if the query point is outside the closed ring, +1 if the
-    /// query point is precisely on the closed ring (per MgMathUtility::DblCmp),
-    /// or +2 if the query point is inside the closed ring.
-    /// </returns>
-    /// <remarks>
-    /// Currently, this function calculates the envelope of the provided closed
-    /// ring in order to determine a point which is known to be outside of the
-    /// closed ring.  An overloaded function which accepts a point known to be
-    /// outside the closed ring which is a lot faster is also available.
-    /// </remarks>
-    static INT32 PointIsInPolygon (MgCoordinateIterator* polyItr,MgCoordinate* queryPoint);
-
-    ///////////////////////////////////////////////////////////////////////////////
-    /// <summary>
-    /// Determines a point guaranteed to be ouside the provided polygon.
-    /// </summary>
-    /// <param name="polyItr">
-    /// An iterator of the closed ring which is the subject polygon.
-    /// </param>
-    /// <returns>
-    /// Returns an <c>MgCoordinateXY</c> point which is guaranteed to be ooutside
-    /// the provided polygon.
-    /// </returns>
-    static MgCoordinate* PointOutsidePolygon (MgCoordinateIterator* polyItr);
-
-    ///////////////////////////////////////////////////////////////////////////////
-    /// <summary>
-    /// Determines if the provided point is inside (or actually on) the closed
-    /// ring provided by the <c>polyItr</c> argument.
-    /// </summary>
-    /// <param name="polyItr">
-    /// An iterator of the closed ring which is the subject polygon.
-    /// </param>
-    /// <param name="outsidePoint">
-    /// A 2D point which is known to be outside of the provided polygon.  Can
-    /// be obtained from <c>PointOutsidePolygon</c>.
-    /// <param name="queryPoint">
-    /// The 2D point whose status is to be determined.
-    /// </param>
-    /// <returns>
-    /// Returns zero if the query point is outside the closed ring, +1 if the
-    /// query point is precisely on the closed ring (per MgMathUtility::DblCmp),
-    /// or +2 if the query point is inside the closed ring.
-    /// </returns>
-    /// <remarks>
-    /// This function requires that the calling module supply a point known to
-    /// be outside the polygon.  Thus, higher performance can be obtained when
-    /// multiple queries against the same polygon are required.
-    /// </remarks>
-    static INT32 PointIsInPolygon (MgCoordinateIterator* polyItr,MgCoordinate* outsidePoint,
-                                                                 MgCoordinate* queryPoint);
-
-    ///////////////////////////////////////////////////////////////////////////////
-    /// <summary>
-    /// Clips a line string to an arbitrary polygon returning a collection of line
-    /// strings which represent the portions of the provided line string which are
-    /// inside the provided closed ring.
-    /// </summary>
-    /// <param name="polyItr">
-    /// An iterator to the closed ring to which the provided line string is to be
-    /// clipped.
-    /// </param>
-    /// <param name="lineItr">
-    /// An iterator for the line string which is to be clipped.
-    /// </param>
-    /// <param name="includeCollinear">
-    /// A true value indicates that segments, and/or portions of segments,
-    /// of the line string to be clipped which are collinear with the clipping
-    /// boundary are to be considered to be inside the clipping boundary.  A
-    /// false value causes collinear segments (or portions thereof) to be
-    /// outside the clipping boundary and thus excluded from the returned
-    /// line string collection.
-    /// </param>
-    /// <returns>
-    /// A collection of line string objects which represents the portions of the
-    /// provided line string which are inside of the provided closed ring.  This
-    /// collection may be empty; a null pointer is never returned.
-    /// </returns>
-    static MgLineStringCollection* ClipStringToPolygon (MgCoordinateIterator* polyItr,
-                                                        MgCoordinateIterator* lineItr,
-                                                        bool includeCollinear = false);
-protected:
-
-    MgSpatialUtility() {};
-    ~MgSpatialUtility() {};
-
-    static void AppendLinearizedCurveSegmentsToDistinctCollection(
-        MgCoordinateCollection * distinctPositions,
-        MgCurveSegmentCollection * curveSegments,
-        double maxSpacing,
-        double maxOffset);
-
-    static void AppendPositionsToDistinctCollection(
-        MgCoordinateCollection * distinctPositions,
-        MgCoordinateCollection * positionsToAppend);
-
-    // Values which are stored in the M member of the internally used point
-    // collection; i.e. the status of each point relative to the clip ploygon.
-    static const INT32 StringPointNotDetermined = 0;
-    static const INT32 StringPointIsOutside     = 1;
-    static const INT32 StringPointIsOnBoundary  = 2;
-    static const INT32 StringPointIsInside      = 3;
-
-    // Values which are stored in the Z member of the internally used point
-    // collection; i.e. the status of each segment relative to the clip
-    // ploygon.  The value stored refers to the segment which starts at
-    // the point in which the segment status is stored.
-    static const INT32 StringSegNotDetermined = 0;
-    static const INT32 StringSegIsOutside     = 1;
-    static const INT32 StringSegIsCollinear   = 2;
-    static const INT32 StringSegIsInside      = 3;
-
-    static MgCoordinateCollection* StringClipPhaseOne (MgCoordinateIterator* lineString,
-                                                       MgCoordinateIterator* itrPolygon);
-    static void StringClipPhaseTwo (MgCoordinateIterator* lineString,MgCoordinateIterator* itrPolygon);
-    static void StringClipPhaseThree (MgCoordinateIterator* lineString,MgCoordinateIterator* itrPolygon);
-    static MgLineStringCollection* StringClipPhaseFour (MgCoordinateIterator* lineString,bool collinear = false,
-                                                                                         bool outside = false);
-};
-/// \endcond
-
-#endif

Modified: trunk/MgDev/Common/Geometry/Spatial/SpatialUtilityCircularArc.cpp
===================================================================
--- trunk/MgDev/Common/Geometry/Spatial/SpatialUtilityCircularArc.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Geometry/Spatial/SpatialUtilityCircularArc.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -16,7 +16,7 @@
 //
 
 #include "GeometryCommon.h"
-#include "SpatialUtility.h"
+#include "SpatialUtilities.h"
 #include "SpatialUtilityCircularArc.h"
 #include "MathUtility.h"
 

Modified: trunk/MgDev/Common/MapGuideCommon/Services/ProxyRenderingService.cpp
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/Services/ProxyRenderingService.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/MapGuideCommon/Services/ProxyRenderingService.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -282,6 +282,28 @@
     return (MgByteReader*)cmd.GetReturnValue().val.m_obj;
 }
 
+MgByteReader * MgProxyRenderingService::RenderTileMVT(MgMap * map, CREFSTRING baseMapLayerGroupName, INT32 x, INT32 y, INT32 z, INT32 dpi)
+{
+    MgCommand cmd;
+    cmd.ExecuteCommand(m_connProp,                      // Connection
+        MgCommand::knObject,                            // Return type expected
+        MgRenderingServiceOpId::RenderTileMVT,          // Command Code
+        7,                                              // No of arguments
+        Rendering_Service,                              // Service Id
+        BUILD_VERSION(4,0,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::knNone);                             // End of arguments
+
+    SetWarning(cmd.GetWarningObject());
+
+    return (MgByteReader*)cmd.GetReturnValue().val.m_obj;
+}
+
 /////////////////////////////////////////////////////////////////
 /// <summary>
 /// Renders all dynamic layers in the specified MgMap to a dynamic overlay image

Modified: trunk/MgDev/Common/MapGuideCommon/Services/ProxyRenderingService.h
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/Services/ProxyRenderingService.h	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/MapGuideCommon/Services/ProxyRenderingService.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -322,6 +322,46 @@
 
     /////////////////////////////////////////////////////////////////
     /// \brief
+    /// Returns the specified MVT (Mapbox Vector 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.
+    ///
+    /// \return
+    /// A byte reader containing the rendered tile image.
+    ///
+    /// \since 4.0
+    virtual MgByteReader* RenderTileMVT(
+        MgMap* map,
+        CREFSTRING baseMapLayerGroupName,
+        INT32 x,
+        INT32 y,
+        INT32 z,
+        INT32 dpi);
+
+    /////////////////////////////////////////////////////////////////
+    /// \brief
     /// Renders all dynamic layers in the specified MgMap to a dynamic overlay image
     /// with a transparent background. The center, scale, size, and layers to be
     /// rendered are defined by the specified map instance.  The format parameter

Modified: trunk/MgDev/Common/MapGuideCommon/Services/RenderingDefs.h
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/Services/RenderingDefs.h	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/MapGuideCommon/Services/RenderingDefs.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -50,6 +50,7 @@
     static const int RenderMetatile             = 0x1111E916;
     static const int RenderTileFromMetaTile     = 0x1111E917;
     static const int RenderMetatileXYZ          = 0x1111E918;
+    static const int RenderTileMVT              = 0x1111E919;
 };
 /// \endcond
 

Modified: trunk/MgDev/Common/MapGuideCommon/Services/RenderingService.h
===================================================================
--- trunk/MgDev/Common/MapGuideCommon/Services/RenderingService.h	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/MapGuideCommon/Services/RenderingService.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -338,6 +338,46 @@
 
     /////////////////////////////////////////////////////////////////
     /// \brief
+    /// Returns the specified MVT (Mapbox Vector 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.
+    ///
+    /// \return
+    /// A byte reader containing the rendered tile image.
+    ///
+    /// \since 4.0
+    virtual MgByteReader* RenderTileMVT(
+        MgMap* map,
+        CREFSTRING baseMapLayerGroupName,
+        INT32 x,
+        INT32 y,
+        INT32 z,
+        INT32 dpi) = 0;
+
+    /////////////////////////////////////////////////////////////////
+    /// \brief
     /// Renders all dynamic layers in the specified MgMap to a dynamic overlay image
     /// with a transparent background. The center, scale, size, and layers to be
     /// rendered are defined by the specified map instance.  The format parameter

Modified: trunk/MgDev/Common/Renderers/CMakeLists.txt
===================================================================
--- trunk/MgDev/Common/Renderers/CMakeLists.txt	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Renderers/CMakeLists.txt	2019-10-06 12:18:03 UTC (rev 9630)
@@ -40,6 +40,8 @@
     KmlIconStyle.cpp
     KmlRenderer.cpp
     MapQuantization.cpp
+    mvt/mvt_tile.cpp
+    MVTRenderer.cpp
     ObservationMesh.cpp
     RenderUtil.cpp
     RS_ByteData.cpp

Copied: trunk/MgDev/Common/Renderers/MVTRenderer.cpp (from rev 9629, sandbox/jng/mvt_alt/Common/Renderers/MVTRenderer.cpp)
===================================================================
--- trunk/MgDev/Common/Renderers/MVTRenderer.cpp	                        (rev 0)
+++ trunk/MgDev/Common/Renderers/MVTRenderer.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -0,0 +1,871 @@
+//
+//  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 "MVTRenderer.h"
+#include "RS_FeatureReader.h"
+#include "UnicodeString.h"
+#include <cmath>
+#include "mvt/gpb.h"
+#include "mvt/mvt_tile.h"
+
+// =========================== MVTRenderer overview =============================== //
+//
+// MVTRenderer is really just an adapter to connect our Stylizer to MVTTile
+//
+// So the responsibility of MVTRenderer is simply to:
+//
+//  - Create a new MVTTileLayer instance on every StartLayer()
+//  - Stash the RS_FeatureReader pointer on every StartFeature() call (we need this for writing properties)
+//  - For every ProcessXXX() call
+//    - Create a MVTTileLayerFeature instance
+//    - Write the active property map
+//    - Encode the geometry coordinates
+//    - Add the MVTTileLayerFeature if encoding succeeds
+//
+// ================================================================================ //
+
+// WebMercator related constants
+constexpr double kmSPHERICAL_RADIUS = 6378137.0;
+constexpr double kmMAX_GM = kmSPHERICAL_RADIUS * M_PI;  // 20037508.342789244
+
+static unsigned GetCmdCountCombined(unsigned int nCmdId,
+    unsigned int nCmdCount)
+{
+    return (nCmdId | (nCmdCount << 3));
+}
+
+
+struct MVTRenderer::MVTImpl {
+    MVTImpl(MVTRenderer* parent, int x, int y, int z)
+        : m_activeLayer(nullptr)
+        , m_nExtent(knDEFAULT_EXTENT)
+        , m_tileX(x)
+        , m_tileY(y)
+        , m_tileZ(z)
+        //InitWebMercatorTilingScheme
+        , m_dfTopX(-kmMAX_GM)
+        , m_dfTopY(kmMAX_GM)
+        , m_dfTileDim0(2 * kmMAX_GM)
+        , m_parent(parent)
+    { }
+
+    ~MVTImpl()
+    {
+
+    }
+
+    void SetActiveLayerBuilder(const std::string& name)
+    {
+        m_activeLayer = std::make_shared<MVTTileLayer>();
+        m_activeLayer->setName(name);
+        m_tile.addLayer(m_activeLayer);
+    }
+
+    void ClearActiveLayerBuilder()
+    {
+        m_activeLayer.reset();
+    }
+
+    void ConvertToTileCoords(double dfX,
+        double dfY,
+        int& nX,
+        int& nY,
+        double dfTopX,
+        double dfTopY,
+        double dfTileDim) const
+    {
+        nX = static_cast<int>(std::round((dfX - dfTopX) * m_nExtent / dfTileDim));
+        nY = static_cast<int>(std::round((dfTopY - dfY) * m_nExtent / dfTileDim));
+    }
+
+    static void AddActiveFeatureProperties(RS_FeatureReader* activeFeature, MVTTileLayer* layer, MVTTileLayerFeature* feature)
+    {
+        TrySetFeatureId(feature, activeFeature);
+
+        int count;
+        auto propNames = activeFeature->GetPropNames(count);
+        for (int i = 0; i < count; i++)
+        {
+            auto name = propNames[i];
+            if (activeFeature->IsNull(name))
+            {
+                continue;
+            }
+
+            MVTTileLayerValue tv;
+
+            std::string mbName;
+            UnicodeString::WideCharToMultiByte(name, mbName);
+            auto propType = activeFeature->GetPropertyType(name);
+            switch (propType)
+            {
+            case FdoDataType_Boolean:
+                tv.setBoolValue(activeFeature->GetBoolean(name));
+                break;
+            case FdoDataType_Byte:
+                tv.setIntValue(activeFeature->GetByte(name));
+                break;
+            case FdoDataType_DateTime:
+            {
+                auto dt = activeFeature->GetAsString(name);
+                std::string sDt;
+                UnicodeString::WideCharToMultiByte(dt, sDt);
+                tv.setStringValue(sDt);
+                break;
+            }
+            case FdoDataType_Decimal:
+            case FdoDataType_Double:
+                tv.setDoubleValue(activeFeature->GetDouble(name));
+                break;
+            case FdoDataType_Int16:
+                tv.setIntValue(activeFeature->GetInt16(name));
+                break;
+            case FdoDataType_Int32:
+                tv.setIntValue(activeFeature->GetInt32(name));
+                break;
+            case FdoDataType_Int64:
+                tv.setIntValue(activeFeature->GetInt64(name));
+                break;
+            case FdoDataType_Single:
+                tv.setFloatValue(activeFeature->GetSingle(name));
+                break;
+            case FdoDataType_String:
+            {
+                auto s = activeFeature->GetString(name);
+                std::string sVal;
+                UnicodeString::WideCharToMultiByte(s, sVal);
+                tv.setStringValue(sVal);
+                break;
+            }
+            case FdoDataType_BLOB: //Not handled
+            case FdoDataType_CLOB:
+                break;
+            }
+
+            auto nKey = layer->addKey(mbName);
+            auto nVal = layer->addValue(tv);
+            feature->addTag(nKey);
+            feature->addTag(nVal);
+        }
+    }
+
+    static void TrySetFeatureId(MVTTileLayerFeature* feature, RS_FeatureReader* activeFeature)
+    {
+        int count = 0;
+        auto idPropNames = activeFeature->GetIdentPropNames(count);
+        if (count == 1)
+        {
+            auto idName = idPropNames[0];
+            auto ptype = activeFeature->GetPropertyType(idName);
+            switch (ptype)
+            {
+            case FdoDataType_Byte:
+                feature->setId(activeFeature->GetByte(idName));
+                break;
+            case FdoDataType_Int16:
+                feature->setId(activeFeature->GetInt16(idName));
+                break;
+            case FdoDataType_Int32:
+                feature->setId(activeFeature->GetInt32(idName));
+                break;
+            case FdoDataType_Int64:
+                feature->setId(activeFeature->GetInt64(idName));
+                break;
+            }
+        }
+    }
+
+    bool EncodePolygonGeometry(MVTTileLayerFeature* poGPBFeature,
+                               LineBuffer* lb,
+                               int cStart, int cEnd,
+                               double dfTopX,
+                               double dfTopY,
+                               double dfTileDim,
+                               bool bCanRecurse,
+                               int& nLastX,
+                               int& nLastY) const
+    {
+        for (int i = cStart; i <= cEnd; i++)
+        {
+            const bool bWriteLastPoint = false;
+            const bool bReverseOrder = (i > 0);
+            const GUInt32 nMinLineTo = 2;
+            const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
+            const int nLastXOri = nLastX;
+            const int nLastYOri = nLastY;
+
+            bool bSuccess =
+                EncodeLineStringGeometry(poGPBFeature, lb, i,
+                    bWriteLastPoint, bReverseOrder,
+                    nMinLineTo,
+                    dfTopX, dfTopY, dfTileDim, nLastX, nLastY);
+            
+            if (!bSuccess)
+            {
+                if (i == 0)
+                    return false;
+                continue;
+            }
+            poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_CLOSEPATH, 1));
+        }
+        return true;
+    }
+
+    bool EncodePolygon(MVTTileLayerFeature* feature, LineBuffer* lb)
+    {
+        double dfTileDim = m_dfTileDim0 / (1 << m_tileZ);
+        double dfTopX = m_dfTopX + m_tileX * dfTileDim;
+        double dfTopY = m_dfTopY - m_tileY * dfTileDim;
+        int nLastX = 0;
+        int nLastY = 0;
+        bool bGeomOK = false;
+
+        int cStart = 0;
+        int geom_count = lb->geom_count();
+        for (int i = 0; i < geom_count; i++)
+        {
+            int cSize = lb->geom_size(i);
+
+            bGeomOK |= EncodePolygonGeometry(feature, lb,
+                cStart, cSize - 1,
+                dfTopX, dfTopY, dfTileDim,
+                true,
+                nLastX, nLastY);
+
+            cStart += cSize;
+        }
+
+        return bGeomOK;
+    }
+
+    void ProcessPolygon(RS_FeatureReader* activeFeature, LineBuffer * lb)
+    {
+        auto feature = std::make_shared<MVTTileLayerFeature>();
+        feature->setType(MVTTileLayerFeature::GeomType::POLYGON);
+        AddActiveFeatureProperties(activeFeature, m_activeLayer.get(), feature.get());
+        if (EncodePolygon(feature.get(), lb))
+            m_activeLayer->addFeature(feature);
+    }
+
+    bool EncodeLineStringGeometry(MVTTileLayerFeature* poGPBFeature,
+                                  LineBuffer* lb,
+                                  int lineStrIdx,
+                                  bool bWriteLastPoint,
+                                  bool bReverseOrder,
+                                  GUInt32 nMinLineTo,
+                                  double dfTopX,
+                                  double dfTopY,
+                                  double dfTileDim,
+                                  int& nLastX,
+                                  int& nLastY) const
+    {
+        const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
+        const int nLastXOri = nLastX;
+        const int nLastYOri = nLastY;
+        GUInt32 nLineToCount = 0;
+        const int nPoints = lb->cntr_size(lineStrIdx) - (bWriteLastPoint ? 0 : 1);
+        int nFirstX = 0;
+        int nFirstY = 0;
+        int nLastXValid = nLastX;
+        int nLastYValid = nLastY;
+        auto pointOffset = lb->contour_start_point(lineStrIdx);
+        for (int i = 0; i < nPoints; i++)
+        {
+            int nX, nY;
+            int nSrcIdx = bReverseOrder ? lb->cntr_size(lineStrIdx) - 1 - i : i;
+            double dfX = lb->x_coord(nSrcIdx + pointOffset);
+            double dfY = lb->y_coord(nSrcIdx + pointOffset);
+
+            ConvertToTileCoords(dfX, dfY, nX, nY,
+                dfTopX, dfTopY, dfTileDim);
+            int nDiffX = nX - nLastX;
+            int nDiffY = nY - nLastY;
+            if (i == 0 || nDiffX != 0 || nDiffY != 0)
+            {
+                if (i > 0)
+                {
+                    nLineToCount++;
+                    if (nLineToCount == 1)
+                    {
+                        poGPBFeature->addGeometry(
+                            GetCmdCountCombined(knCMD_MOVETO, 1));
+                        const int nLastDiffX = nLastX - nLastXOri;
+                        const int nLastDiffY = nLastY - nLastYOri;
+                        poGPBFeature->addGeometry(EncodeSInt(nLastDiffX));
+                        poGPBFeature->addGeometry(EncodeSInt(nLastDiffY));
+                        
+                        // To be modified later
+                        poGPBFeature->addGeometry(
+                            GetCmdCountCombined(knCMD_LINETO, 0));
+                    }
+
+                    poGPBFeature->addGeometry(EncodeSInt(nDiffX));
+                    poGPBFeature->addGeometry(EncodeSInt(nDiffY));
+                }
+                else
+                {
+                    nFirstX = nX;
+                    nFirstY = nY;
+                }
+                nLastXValid = nLastX;
+                nLastYValid = nLastY;
+                nLastX = nX;
+                nLastY = nY;
+            }
+        }
+
+        // If last point of ring is identical to first one, discard it
+        if (nMinLineTo == 2 && nLineToCount > 0 &&
+            nFirstX == nLastX && nFirstY == nLastY)
+        {
+            poGPBFeature->resizeGeometryArray(
+                poGPBFeature->getGeometryCount() - 2);
+            nLineToCount--;
+            nLastX = nLastXValid;
+            nLastY = nLastYValid;
+        }
+
+        if (nLineToCount >= nMinLineTo)
+        {
+            // Patch actual number of points in LINETO command
+            poGPBFeature->setGeometry(nInitialSize + 3,
+                GetCmdCountCombined(knCMD_LINETO, nLineToCount));
+            return true;
+        }
+        else
+        {
+            poGPBFeature->resizeGeometryArray(nInitialSize);
+            nLastX = nLastXOri;
+            nLastY = nLastYOri;
+            return false;
+        }
+    }
+
+    bool EncodePolyine(MVTTileLayerFeature* feature, LineBuffer* lb)
+    {
+        double dfTileDim = m_dfTileDim0 / (1 << m_tileZ);
+        double dfTopX = m_dfTopX + m_tileX * dfTileDim;
+        double dfTopY = m_dfTopY - m_tileY * dfTileDim;
+        bool bGeomOK = false;
+        const bool bWriteLastPoint = true;
+        const bool bReverseOrder = false;
+        const GUInt32 nMinLineTo = 1;
+        int nLastX = 0;
+        int nLastY = 0;
+        //TODO: Can probably fold everything into the MultiLineString code path here
+        if (lb->cntr_count() == 1)
+        {
+            bGeomOK = EncodeLineStringGeometry(feature, lb, 0,
+                bWriteLastPoint, bReverseOrder,
+                nMinLineTo,
+                dfTopX, dfTopY, dfTileDim,
+                nLastX, nLastY);
+        }
+        else //MultiLineString
+        {
+            for (auto i = 0; i < lb->cntr_count(); i++)
+            {
+                bool bSubGeomOK = EncodeLineStringGeometry(
+                    feature, lb, i,
+                    bWriteLastPoint, bReverseOrder,
+                    nMinLineTo,
+                    dfTopX, dfTopY, dfTileDim,
+                    nLastX, nLastY);
+                bGeomOK |= bSubGeomOK;
+            }
+        }
+        return bGeomOK;
+    }
+
+    void ProcessPolyline(RS_FeatureReader* activeFeature, LineBuffer * lb)
+    {
+        auto feature = std::make_shared<MVTTileLayerFeature>();
+        feature->setType(MVTTileLayerFeature::GeomType::LINESTRING);
+        AddActiveFeatureProperties(activeFeature, m_activeLayer.get(), feature.get());
+        if (EncodePolyine(feature.get(), lb))
+            m_activeLayer->addFeature(feature);
+    }
+
+    bool EncodePoint(MVTTileLayerFeature* feature, LineBuffer* lb)
+    {
+        double dfTileDim = m_dfTileDim0 / (1 << m_tileZ);
+        double dfTopX = m_dfTopX + m_tileX * dfTileDim;
+        double dfTopY = m_dfTopY - m_tileY * dfTileDim;
+
+        bool bGeomOK = false;
+        if (lb->point_count() == 1)
+        {
+            int nX, nY;
+            auto dfX = lb->x_coord(0);
+            auto dfY = lb->y_coord(0);
+            bGeomOK = true;
+            ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY, dfTileDim);
+
+            feature->addGeometry(GetCmdCountCombined(knCMD_MOVETO, 1));
+            feature->addGeometry(EncodeSInt(nX));
+            feature->addGeometry(EncodeSInt(nY));
+        }
+        else //MultiPoint
+        {
+            std::set<std::pair<int, int>> oSetUniqueCoords;
+            feature->addGeometry(GetCmdCountCombined(knCMD_MOVETO, 0)); // To be modified later
+            int nLastX = 0;
+            int nLastY = 0;
+            for (auto i = 0; i < lb->point_count(); i++)
+            {
+                int nX, nY;
+                auto dfX = lb->x_coord(i);
+                auto dfY = lb->y_coord(i);
+                ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY, dfTileDim);
+                if (oSetUniqueCoords.find(std::pair<int, int>(nX, nY)) ==
+                    oSetUniqueCoords.end())
+                {
+                    oSetUniqueCoords.insert(std::pair<int, int>(nX, nY));
+
+                    int nDiffX = nX - nLastX;
+                    int nDiffY = nY - nLastY;
+                    feature->addGeometry(EncodeSInt(nDiffX));
+                    feature->addGeometry(EncodeSInt(nDiffY));
+                    nLastX = nX;
+                    nLastY = nY;
+                }
+            }
+            GUInt32 nPoints = static_cast<GUInt32>(oSetUniqueCoords.size());
+            bGeomOK = nPoints > 0;
+            feature->setGeometry(0, GetCmdCountCombined(knCMD_MOVETO, nPoints));
+        }
+        return bGeomOK;
+    }
+
+    void ProcessMarker(RS_FeatureReader* activeFeature, LineBuffer * lb)
+    {
+        auto feature = std::make_shared<MVTTileLayerFeature>();
+        feature->setType(MVTTileLayerFeature::GeomType::POINT);
+        AddActiveFeatureProperties(activeFeature, m_activeLayer.get(), feature.get());
+        if (EncodePoint(feature.get(), lb))
+            m_activeLayer->addFeature(feature);
+    }
+
+    unsigned char* GetMVTContent(size_t& size)
+    {
+        size = m_tile.getSize();
+        GByte* pabyBuffer = new GByte[size];
+        m_tile.write(pabyBuffer);
+        return pabyBuffer;
+    }
+
+    void ReleaseMVTContent(unsigned char* buf)
+    {
+        delete[] buf;
+    }
+
+    void SetTileCoords(const int x, const int y, const int z)
+    {
+        m_tileX = x;
+        m_tileY = y;
+        m_tileZ = z;
+    }
+
+private:
+    MVTTile m_tile;
+    std::shared_ptr<MVTTileLayer> m_activeLayer;
+    MVTRenderer* m_parent;
+
+    int m_tileX;
+    int m_tileY;
+    int m_tileZ;
+
+    double m_dfTopX;
+    double m_dfTopY;
+    double m_dfTileDim0;
+
+    unsigned int m_nExtent;
+};
+
+MVTRenderer::MVTRenderer(int x, int y, int z)
+    : m_impl(new MVTRenderer::MVTImpl(this, x, y, z)) //MVTImpl only stashes this pointer in its ctor, nothing is called on it yet
+    , m_mapInfo(nullptr)
+    , m_layerInfo(nullptr)
+    , m_fcInfo(nullptr)
+{
+
+}
+
+MVTRenderer::~MVTRenderer()
+{ }
+
+//Called by the stylizer when rendering is about to begin on the map
+void MVTRenderer::StartMap(RS_MapUIInfo * /*mapInfo*/, RS_Bounds & extents, double mapScale, double dpi, double metersPerUnit, CSysTransformer * /*xformToLL*/)
+{
+    m_mapScale = mapScale;
+    m_dpi = dpi;
+    m_metersPerUnit = metersPerUnit;
+    m_extents = extents;
+
+    // find scale used to convert to pixel coordinates
+    // need to take aspect ratios into account
+    /*
+    double arDisplay = (double)m_width / (double)m_height;
+    double arMap = m_extents.width() / m_extents.height();
+
+    double scale;
+    if (arDisplay > arMap)
+        scale = (double)m_height / m_extents.height();
+    else
+    */    
+    double wScale = (double)m_width / m_extents.width();
+    double hScale = (double)m_height / m_extents.height();
+
+    m_xform.x0 = wScale;
+    m_xform.x1 = 0.0;
+    m_xform.x2 = -wScale * m_extents.minx;
+    m_xform.y0 = 0.0;
+    m_xform.y1 = hScale;
+    m_xform.y2 = -hScale * m_extents.miny;
+
+    m_ixform.x0 = 1.0 / wScale;
+    m_ixform.x1 = 0.0;
+    m_ixform.x2 = m_extents.minx;
+    m_ixform.y0 = 0.0;
+    m_ixform.y1 = m_ixform.x0;
+    m_ixform.y2 = m_extents.miny;
+
+    double metersPerPixel = METERS_PER_INCH / m_dpi;
+
+    // compute drawing scale
+    // drawing scale is map scale converted to [mapping units] / [pixels]
+    m_drawingScale = m_mapScale * metersPerPixel / m_metersPerUnit;
+}
+
+//Called by the stylizer when rendering is about to begin on the map
+void MVTRenderer::EndMap()
+{
+    // clear the map info
+    m_mapInfo = nullptr;
+}
+
+//Called by the stylizer when rendering is about to begin on the map for the given layer
+void MVTRenderer::StartLayer(RS_LayerUIInfo * layerInfo, RS_FeatureClassInfo * classInfo)
+{
+    m_layerInfo = layerInfo;
+    m_fcInfo = classInfo;
+
+    std::string name;
+    UnicodeString::WideCharToMultiByte(m_layerInfo->name().c_str(), name);
+
+    m_impl->SetActiveLayerBuilder(name);
+}
+
+//Called by the stylizer when rendering is about to begin on the map for the layer indicated by StartLayer()
+void MVTRenderer::EndLayer()
+{
+    // clear the layer/feature info
+    m_layerInfo = nullptr;
+    m_fcInfo = nullptr;
+
+    m_impl->ClearActiveLayerBuilder();
+}
+
+//Called by the stylizer when rendering is about to begin on the map for the given feature
+void MVTRenderer::StartFeature(RS_FeatureReader * feature, bool /*initialPass*/, const RS_String * /*tooltip*/, const RS_String * /*url*/, const RS_String * /*theme*/, double /*zOffset*/, double /*zExtrusion*/, RS_ElevationType /*zOffsetType*/)
+{
+    m_activeFeature = feature;
+}
+
+//Entry point for polygon rendering. Perform any pre-rendering operations.
+void MVTRenderer::ProcessPolygon(LineBuffer * lb, RS_FillStyle & /*fill*/)
+{
+    //Optimize LB before sending to MVT tile writer
+    auto workbuffer = lb->Optimize(m_drawingScale, m_pPool);
+    std::unique_ptr<LineBuffer> spLB(workbuffer);
+
+    //Also clip the geometry to the extents if required as we do not want to encode
+    //"out of tile" coordinates
+    auto clipped = workbuffer->Clip(m_extents, LineBuffer::ctArea, m_pPool);
+    if (workbuffer != clipped)
+    {
+        if (spLB.get())
+        {
+            LineBufferPool::FreeLineBuffer(m_pPool, spLB.release());
+        }
+
+        workbuffer = clipped;
+        spLB.reset(workbuffer);
+    }
+
+    if (workbuffer)
+        m_impl->ProcessPolygon(m_activeFeature, workbuffer);
+
+    if (spLB.get())
+        LineBufferPool::FreeLineBuffer(m_pPool, spLB.release());
+}
+
+//Entry point for polyline rendering. Perform any pre-rendering operations.
+void MVTRenderer::ProcessPolyline(LineBuffer * lb, RS_LineStroke & /*lsym*/)
+{
+    //Optimize LB before sending to MVT tile writer
+    auto workbuffer = lb->Optimize(m_drawingScale, m_pPool);
+    std::unique_ptr<LineBuffer> spLB(workbuffer);
+
+    //Also clip the geometry to the extents if required as we do not want to encode
+    //"out of tile" coordinates
+    auto clipped = workbuffer->Clip(m_extents, LineBuffer::ctLine, m_pPool);
+    if (workbuffer != clipped)
+    {
+        if (spLB.get())
+        {
+            LineBufferPool::FreeLineBuffer(m_pPool, spLB.release());
+        }
+
+        workbuffer = clipped;
+        spLB.reset(workbuffer);
+    }
+
+    if (workbuffer)
+        m_impl->ProcessPolyline(m_activeFeature, workbuffer);
+
+    if (spLB.get())
+        LineBufferPool::FreeLineBuffer(m_pPool, spLB.release());
+}
+
+//Entry point for raster/image rendering. Perform any pre-rendering operations.
+void MVTRenderer::ProcessRaster(unsigned char * /*data*/, int /*length*/, RS_ImageFormat /*format*/, int /*width*/, int /*height*/, RS_Bounds & /*extents*/, TransformMesh * /*xformMesh*/)
+{
+    //This will generally result in a call to DrawScreenRaster() after processing.
+    //
+    //If your renderer only concerns vector data, this method can be left blank
+}
+
+//Entry point for marker rendering. Perform any pre-rendering operations.
+void MVTRenderer::ProcessMarker(LineBuffer * lb, RS_MarkerDef & /*mdef*/, bool /*allowOverpost*/, RS_Bounds * /*bounds*/)
+{
+    //vtzero is very finicky about redundant coordinates, so feed vtzero an optimized LineBuffer
+    //that should weed such coordinates out
+    auto workbuffer = lb->Optimize(m_drawingScale, m_pPool);
+    std::unique_ptr<LineBuffer> spLB(workbuffer);
+
+    //Also clip the geometry to the extents if required as we do not want to encode
+    //"out of tile" coordinates
+    auto clipped = workbuffer->Clip(m_extents, LineBuffer::ctPoint, m_pPool);
+    if (workbuffer != clipped)
+    {
+        if (spLB.get())
+        {
+            LineBufferPool::FreeLineBuffer(m_pPool, spLB.release());
+        }
+
+        workbuffer = clipped;
+        spLB.reset(workbuffer);
+    }
+
+    if (workbuffer)
+        m_impl->ProcessMarker(m_activeFeature, workbuffer);
+
+    if (spLB.get())
+        LineBufferPool::FreeLineBuffer(m_pPool, spLB.release());
+}
+
+//Entry point for label group rendering.
+void MVTRenderer::ProcessLabelGroup(RS_LabelInfo * /*labels*/, int /*nlabels*/, const RS_String & /*text*/, RS_OverpostType /*type*/, bool /*exclude*/, LineBuffer * /*path*/, double /*scaleLimit*/)
+{
+    //Most implementations will carry a LabelRenderer member and forward this method call
+    //to it.
+    //
+    //If your renderer does not render labels, this method can be left blank
+}
+
+// Inserts the contents of a given DWF input stream into the current
+// output W2D.  The given coord sys transformation is applied and geometry
+// will be clipped to the RS_Bounds context of the DWFRenderer.
+void MVTRenderer::AddDWFContent(RS_InputStream * /*in*/, CSysTransformer * /*xformer*/, const RS_String & /*section*/, const RS_String & /*passwd*/, const RS_String & /*w2dfilter*/)
+{
+    // This is data from a DWF drawing layer. If your renderer is not intended to support DWF
+    // drawing layers/sources, this method can be left blank.
+}
+
+//Sets the legacy symbol manager which will be used when fetching (DWF) symbol data
+void MVTRenderer::SetSymbolManager(RS_SymbolManager * /*manager*/)
+{
+    //If your renderer does not render DWF-based image symbols, this method can be left blank
+}
+
+RS_MapUIInfo * MVTRenderer::GetMapInfo()
+{
+    return m_mapInfo;
+}
+
+RS_LayerUIInfo * MVTRenderer::GetLayerInfo()
+{
+    return m_layerInfo;
+}
+
+RS_FeatureClassInfo * MVTRenderer::GetFeatureClassInfo()
+{
+    return m_fcInfo;
+}
+
+double MVTRenderer::GetMapScale()
+{
+    return m_mapScale;
+}
+
+double MVTRenderer::GetDrawingScale()
+{
+    return m_drawingScale;
+}
+
+double MVTRenderer::GetMetersPerUnit()
+{
+    return m_metersPerUnit;
+}
+
+double MVTRenderer::GetDpi()
+{
+    return m_dpi;
+}
+
+RS_Bounds & MVTRenderer::GetBounds()
+{
+    return m_extents;
+}
+
+bool MVTRenderer::RequiresClipping()
+{
+    return false;
+}
+
+bool MVTRenderer::RequiresLabelClipping()
+{
+    return false;
+}
+
+//Return true if this renderer can handle 3D geometries
+bool MVTRenderer::SupportsZ()
+{
+    return false;
+}
+
+//Render out the given polyline using the provided fill style
+void MVTRenderer::DrawScreenPolyline(LineBuffer * /*polyline*/, const SE_Matrix * /*xform*/, const SE_LineStroke & /*lineStroke*/)
+{
+
+}
+
+//Render out the given polygon using the provided fill style
+void MVTRenderer::DrawScreenPolygon(LineBuffer * /*polygon*/, const SE_Matrix * /*xform*/, unsigned int /*fill*/)
+{
+
+}
+
+//Render out the given raster data.
+void MVTRenderer::DrawScreenRaster(unsigned char * /*data*/, int /*length*/, RS_ImageFormat /*format*/, int /*native_width*/, int /*native_height*/, double /*x*/, double /*y*/, double /*w*/, double /*h*/, double /*angleDeg*/)
+{
+    //If your renderer only concerns vector data, this method can be left blank
+}
+
+//Render out the given raster data.
+void MVTRenderer::DrawScreenRaster(unsigned char * /*data*/, int /*length*/, RS_ImageFormat /*format*/, int /*native_width*/, int /*native_height*/, double /*x*/, double /*y*/, double /*w*/, double /*h*/, double /*angleDeg*/, double /*alpha*/)
+{
+    //If your renderer only concerns vector data, this method can be left blank
+}
+
+//Render out the given text.
+void MVTRenderer::DrawScreenText(const RS_TextMetrics & /*tm*/, RS_TextDef & /*tdef*/, double /*insx*/, double /*insy*/, RS_F_Point * /*path*/, int /*npts*/, double /*param_position*/)
+{
+    //If your renderer does not render labels, this method can be left blank
+}
+
+bool MVTRenderer::YPointsUp()
+{
+    return false;
+}
+
+void MVTRenderer::GetWorldToScreenTransform(SE_Matrix & xform)
+{
+    xform = m_xform;
+}
+
+void MVTRenderer::WorldToScreenPoint(double & inx, double & iny, double & ox, double & oy)
+{
+    m_xform.transform(inx, iny, ox, oy);
+}
+
+void MVTRenderer::ScreenToWorldPoint(double & inx, double & iny, double & ox, double & oy)
+{
+    m_ixform.transform(inx, iny, ox, oy);
+}
+
+double MVTRenderer::GetScreenUnitsPerMillimeterDevice()
+{
+    return m_dpi / MILLIMETERS_PER_INCH;
+}
+
+double MVTRenderer::GetScreenUnitsPerMillimeterWorld()
+{
+    return m_dpi / MILLIMETERS_PER_INCH / m_mapScale;
+}
+
+double MVTRenderer::GetScreenUnitsPerPixel()
+{
+    return 1.0;
+}
+
+RS_FontEngine * MVTRenderer::GetRSFontEngine()
+{
+    return NULL;
+}
+
+//Entry point for label rendering.
+void MVTRenderer::ProcessSELabelGroup(SE_LabelInfo * /*labels*/, int /*nlabels*/, RS_OverpostType /*type*/, bool /*exclude*/, LineBuffer * /*path*/)
+{
+    //Most implementations will carry a LabelRenderer member and forward this method call
+    //to it.
+    //
+    //If this render does not render labels, this method can be left blank.
+}
+
+//If this renderer can render labels, add and track the given exclusion region.
+//An exclusion region is a region that should not factor into desired label/symbol placement.
+void MVTRenderer::AddExclusionRegion(RS_F_Point * /*fpts*/, int /*npts*/)
+{
+    //Most implementations will carry a LabelRenderer member and forward this method call
+    //to it.
+    //
+    //If this renderer does not render labels. This method can be left blank.
+}
+
+unsigned char* MVTRenderer::GetMVTContent(size_t& size)
+{
+    return m_impl->GetMVTContent(size);
+}
+
+void MVTRenderer::ReleaseMVTContent(unsigned char* buf)
+{
+    m_impl->ReleaseMVTContent(buf);
+}
+
+RS_DesiredPolygonOrientation MVTRenderer::GetDesiredPolygonOrientation()
+{
+    return RS_DesiredPolygonOrientation::Clockwise;
+}
+
+bool MVTRenderer::IsValidTile(unsigned char* buf, size_t size)
+{
+    MVTTile oTileDeserialized;
+    bool bRet = oTileDeserialized.read(buf, buf + size);
+    return bRet;
+}
\ No newline at end of file

Copied: trunk/MgDev/Common/Renderers/MVTRenderer.h (from rev 9629, sandbox/jng/mvt_alt/Common/Renderers/MVTRenderer.h)
===================================================================
--- trunk/MgDev/Common/Renderers/MVTRenderer.h	                        (rev 0)
+++ trunk/MgDev/Common/Renderers/MVTRenderer.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -0,0 +1,106 @@
+//
+//  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 _MVTRENDERER_H_
+#define _MVTRENDERER_H_
+
+#include "Renderers.h"
+#include "SE_Renderer.h"
+#include <vector>
+
+// A renderer that produces Mapbox Vector Tiles (MVT). This renderer exclusively works
+// against an XYZ tile access scheme so it can only be used for rendering XYZ tiles and
+// no other type of rendering
+class MVTRenderer : public SE_Renderer
+{
+public:
+    RENDERERS_API MVTRenderer(int x, int y, int z);
+    RENDERERS_API virtual ~MVTRenderer();
+
+    // Inherited via SE_Renderer
+    RENDERERS_API virtual void StartMap(RS_MapUIInfo * mapInfo, RS_Bounds & extents, double mapScale, double dpi, double metersPerUnit, CSysTransformer * xformToLL);
+    RENDERERS_API virtual void EndMap();
+    RENDERERS_API virtual void StartLayer(RS_LayerUIInfo * layerInfo, RS_FeatureClassInfo * classInfo);
+    RENDERERS_API virtual void EndLayer();
+    RENDERERS_API virtual void StartFeature(RS_FeatureReader * feature, bool initialPass, const RS_String * tooltip = NULL, const RS_String * url = NULL, const RS_String * theme = NULL, double zOffset = 0.0, double zExtrusion = 0.0, RS_ElevationType zOffsetType = RS_ElevationType_RelativeToGround);
+    RENDERERS_API virtual void ProcessPolygon(LineBuffer * lb, RS_FillStyle & fill);
+    RENDERERS_API virtual void ProcessPolyline(LineBuffer * lb, RS_LineStroke & lsym);
+    RENDERERS_API virtual void ProcessRaster(unsigned char * data, int length, RS_ImageFormat format, int width, int height, RS_Bounds & extents, TransformMesh * xformMesh = NULL);
+    RENDERERS_API virtual void ProcessMarker(LineBuffer * lb, RS_MarkerDef & mdef, bool allowOverpost, RS_Bounds * bounds = NULL);
+    RENDERERS_API virtual void ProcessLabelGroup(RS_LabelInfo * labels, int nlabels, const RS_String & text, RS_OverpostType type, bool exclude, LineBuffer * path, double scaleLimit);
+    RENDERERS_API virtual void AddDWFContent(RS_InputStream * in, CSysTransformer * xformer, const RS_String & section, const RS_String & passwd, const RS_String & w2dfilter);
+    RENDERERS_API virtual void SetSymbolManager(RS_SymbolManager * manager);
+    RENDERERS_API virtual RS_MapUIInfo * GetMapInfo();
+    RENDERERS_API virtual RS_LayerUIInfo * GetLayerInfo();
+    RENDERERS_API virtual RS_FeatureClassInfo * GetFeatureClassInfo();
+    RENDERERS_API virtual double GetMapScale();
+    RENDERERS_API virtual double GetDrawingScale();
+    RENDERERS_API virtual double GetMetersPerUnit();
+    RENDERERS_API virtual double GetDpi();
+    RENDERERS_API virtual RS_Bounds & GetBounds();
+    RENDERERS_API virtual bool RequiresClipping();
+    RENDERERS_API virtual bool RequiresLabelClipping();
+    RENDERERS_API virtual bool SupportsZ();
+    RENDERERS_API virtual void DrawScreenPolyline(LineBuffer * polyline, const SE_Matrix * xform, const SE_LineStroke & lineStroke);
+    RENDERERS_API virtual void DrawScreenPolygon(LineBuffer * polygon, const SE_Matrix * xform, unsigned int fill);
+    RENDERERS_API virtual void DrawScreenRaster(unsigned char * data, int length, RS_ImageFormat format, int native_width, int native_height, double x, double y, double w, double h, double angleDeg);
+    RENDERERS_API virtual void DrawScreenRaster(unsigned char * data, int length, RS_ImageFormat format, int native_width, int native_height, double x, double y, double w, double h, double angleDeg, double alpha);
+    RENDERERS_API virtual void DrawScreenText(const RS_TextMetrics & tm, RS_TextDef & tdef, double insx, double insy, RS_F_Point * path, int npts, double param_position);
+    RENDERERS_API virtual bool YPointsUp();
+    RENDERERS_API virtual void GetWorldToScreenTransform(SE_Matrix & xform);
+    RENDERERS_API virtual void WorldToScreenPoint(double & inx, double & iny, double & ox, double & oy);
+    RENDERERS_API virtual void ScreenToWorldPoint(double & inx, double & iny, double & ox, double & oy);
+    RENDERERS_API virtual double GetScreenUnitsPerMillimeterDevice();
+    RENDERERS_API virtual double GetScreenUnitsPerMillimeterWorld();
+    RENDERERS_API virtual double GetScreenUnitsPerPixel();
+    RENDERERS_API virtual RS_FontEngine * GetRSFontEngine();
+    RENDERERS_API virtual void ProcessSELabelGroup(SE_LabelInfo * labels, int nlabels, RS_OverpostType type, bool exclude, LineBuffer * path = NULL);
+    RENDERERS_API virtual void AddExclusionRegion(RS_F_Point * fpts, int npts);
+    RENDERERS_API virtual RS_DesiredPolygonOrientation GetDesiredPolygonOrientation();
+
+    // MVT-specific. Caller responsible for calling ReleaseMVTContent() on the returned
+    // pointer once done
+    RENDERERS_API unsigned char* GetMVTContent(size_t& size);
+    RENDERERS_API void ReleaseMVTContent(unsigned char* buf);
+
+    // For unit testing purposes
+    RENDERERS_API static bool IsValidTile(unsigned char* buf, size_t size);
+
+private:
+    struct MVTImpl;
+    std::unique_ptr<MVTImpl> m_impl;
+
+    RS_FeatureReader* m_activeFeature;
+
+    // map/layer/feature info
+    RS_MapUIInfo* m_mapInfo;
+    RS_LayerUIInfo* m_layerInfo;
+    RS_FeatureClassInfo* m_fcInfo;
+
+    SE_Matrix m_xform;
+    SE_Matrix m_ixform;
+    int m_width;
+    int m_height;
+
+    RS_Bounds m_extents;
+    double m_drawingScale;
+    double m_metersPerUnit;
+    double m_dpi;
+    double m_mapScale;
+};
+
+#endif
\ No newline at end of file

Modified: trunk/MgDev/Common/Renderers/Renderers.vcxproj
===================================================================
--- trunk/MgDev/Common/Renderers/Renderers.vcxproj	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Renderers/Renderers.vcxproj	2019-10-06 12:18:03 UTC (rev 9630)
@@ -215,6 +215,8 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="MapQuantization.cpp" />
+    <ClCompile Include="MVTRenderer.cpp" />
+    <ClCompile Include="mvt\mvt_tile.cpp" />
     <ClCompile Include="SymbolTrans.cpp" />
     <ClCompile Include="DWFRenderer.cpp" />
     <ClCompile Include="EPlotRenderer.cpp" />
@@ -259,6 +261,10 @@
     <ClInclude Include="agg_utfgrid_context.h" />
     <ClInclude Include="MapQuantization.h" />
     <ClInclude Include="MapUTFGrid.h" />
+    <ClInclude Include="MVTRenderer.h" />
+    <ClInclude Include="mvt\gpb.h" />
+    <ClInclude Include="mvt\mvt_port.h" />
+    <ClInclude Include="mvt\mvt_tile.h" />
     <ClInclude Include="SymbolTrans.h" />
     <ClInclude Include="DWFRenderer.h" />
     <ClInclude Include="DWFRSInputStream.h" />

Modified: trunk/MgDev/Common/Renderers/Renderers.vcxproj.filters
===================================================================
--- trunk/MgDev/Common/Renderers/Renderers.vcxproj.filters	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Renderers/Renderers.vcxproj.filters	2019-10-06 12:18:03 UTC (rev 9630)
@@ -25,6 +25,9 @@
     <Filter Include="UTFGridRenderer">
       <UniqueIdentifier>{139cab4d-e977-4ad2-8526-73e40cb9dde1}</UniqueIdentifier>
     </Filter>
+    <Filter Include="MVTRenderer">
+      <UniqueIdentifier>{86e80f69-f211-4769-b8fe-549ba11f2bee}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="SymbolTrans.cpp">
@@ -111,6 +114,12 @@
     <ClCompile Include="UTFGridContent.cpp">
       <Filter>UTFGridRenderer</Filter>
     </ClCompile>
+    <ClCompile Include="mvt\mvt_tile.cpp">
+      <Filter>MVTRenderer</Filter>
+    </ClCompile>
+    <ClCompile Include="MVTRenderer.cpp">
+      <Filter>MVTRenderer</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="SymbolTrans.h">
@@ -202,6 +211,18 @@
     <ClInclude Include="MapUTFGrid.h">
       <Filter>UTFGridRenderer</Filter>
     </ClInclude>
+    <ClInclude Include="mvt\gpb.h">
+      <Filter>MVTRenderer</Filter>
+    </ClInclude>
+    <ClInclude Include="mvt\mvt_tile.h">
+      <Filter>MVTRenderer</Filter>
+    </ClInclude>
+    <ClInclude Include="mvt\mvt_port.h">
+      <Filter>MVTRenderer</Filter>
+    </ClInclude>
+    <ClInclude Include="MVTRenderer.h">
+      <Filter>MVTRenderer</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="Renderers.rc" />

Modified: trunk/MgDev/Common/Stylization/DefaultStylizer.cpp
===================================================================
--- trunk/MgDev/Common/Stylization/DefaultStylizer.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Stylization/DefaultStylizer.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -343,7 +343,9 @@
         try
         {
             if (!features->IsNull(gpName))
-                features->GetGeometry(gpName, lb, xformer);
+            {
+                features->GetGeometry(gpName, lb, xformer, renderer->GetDesiredPolygonOrientation());
+            }
             else
             {
                 // just move on to the next feature

Modified: trunk/MgDev/Common/Stylization/LineBuffer.cpp
===================================================================
--- trunk/MgDev/Common/Stylization/LineBuffer.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Stylization/LineBuffer.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -901,6 +901,10 @@
 
             for (int q=0; q<num_geoms; ++q)
             {
+                // Mark the beginning of a new geometry
+                if (q > 0)
+                    NewGeometry();
+
                 // skip past geometry type of subgeometry
                 // we know it is LineString or Polygon or Point respectively
                 if (is_multi)

Modified: trunk/MgDev/Common/Stylization/RS_FeatureReader.h
===================================================================
--- trunk/MgDev/Common/Stylization/RS_FeatureReader.h	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Stylization/RS_FeatureReader.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -41,7 +41,7 @@
     virtual FdoInt32        GetInt32       (const wchar_t* propertyName) = 0;
     virtual FdoInt64        GetInt64       (const wchar_t* propertyName) = 0;
     virtual const wchar_t*  GetString      (const wchar_t* propertyName) = 0;
-    virtual LineBuffer*     GetGeometry    (const wchar_t* propertyName, LineBuffer* lb, CSysTransformer* xformer) = 0;
+    virtual LineBuffer*     GetGeometry    (const wchar_t* propertyName, LineBuffer* lb, CSysTransformer* xformer, RS_DesiredPolygonOrientation polygonOrientation) = 0;
     virtual RS_Raster*      GetRaster      (const wchar_t* propertyName) = 0;
     virtual const wchar_t*  GetAsString    (const wchar_t* propertyName) = 0;
     virtual RS_InputStream* GetBLOB        (const wchar_t* propertyName) = 0;

Modified: trunk/MgDev/Common/Stylization/Renderer.h
===================================================================
--- trunk/MgDev/Common/Stylization/Renderer.h	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Stylization/Renderer.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -240,6 +240,9 @@
     // line buffer pool access
     virtual SE_BufferPool* GetBufferPool() = 0;
     virtual void SetBufferPool(SE_BufferPool* pool) = 0;
+
+    // States the desired polygon orientation
+    virtual RS_DesiredPolygonOrientation GetDesiredPolygonOrientation() = 0;
 };
 
 #endif

Modified: trunk/MgDev/Common/Stylization/RendererStyles.h
===================================================================
--- trunk/MgDev/Common/Stylization/RendererStyles.h	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Stylization/RendererStyles.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -23,7 +23,14 @@
 
 typedef std::wstring RS_String;
 
+enum RS_DesiredPolygonOrientation
+{
+    Clockwise,
+    CounterClockwise,
+    NotApplicable
+};
 
+
 //////////////////////////////////////////////////////////////////////////////
 enum RS_HAlignment
 {

Modified: trunk/MgDev/Common/Stylization/SE_Renderer.cpp
===================================================================
--- trunk/MgDev/Common/Stylization/SE_Renderer.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Stylization/SE_Renderer.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -722,3 +722,8 @@
 {
     return true;
 }
+
+RS_DesiredPolygonOrientation SE_Renderer::GetDesiredPolygonOrientation()
+{
+    return RS_DesiredPolygonOrientation::NotApplicable;
+}
\ No newline at end of file


Property changes on: trunk/MgDev/Common/Stylization/SE_Renderer.cpp
___________________________________________________________________
Modified: svn:mergeinfo
## -2,6 +2,7 ##
 /branches/2.6/MgDev/Common/Stylization/SE_Renderer.cpp:8340
 /sandbox/adsk/2.4j/Common/Stylization/SE_Renderer.cpp:6327-6445
 /sandbox/jng/createruntimemap/Common/Stylization/SE_Renderer.cpp:7486-7555
+/sandbox/jng/mvt/Common/Stylization/SE_Renderer.cpp:9546-9547
+/sandbox/jng/mvt_alt/Common/Stylization/SE_Renderer.cpp:9604-9629
 /sandbox/jng/v4/Common/Stylization/SE_Renderer.cpp:9511-9519
-/sandbox/rfc94/Common/Stylization/SE_Renderer.cpp:5099-5163
-/trunk/MgDev/Common/Stylization/SE_Renderer.cpp:6250-6326
\ No newline at end of property
+/sandbox/rfc94/Common/Stylization/SE_Renderer.cpp:5099-5163
\ No newline at end of property
Modified: trunk/MgDev/Common/Stylization/SE_Renderer.h
===================================================================
--- trunk/MgDev/Common/Stylization/SE_Renderer.h	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Stylization/SE_Renderer.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -136,6 +136,11 @@
     // overridden.
     STYLIZATION_API virtual bool OptimizeGeometry();
 
+    // Indicate the desired polygon orientation for this renderer. Some renderers may demand
+    // that polygon data passed to it be of a particular orientation. It will be the responsibility
+    // of the Stylizer to make sure this requirement is met.
+    STYLIZATION_API virtual RS_DesiredPolygonOrientation GetDesiredPolygonOrientation();
+
 private:
     void ProcessLineOverlapWrap(LineBuffer* geometry, SE_RenderLineStyle* style);
     void ProcessLineOverlapNone(LineBuffer* geometry, SE_RenderLineStyle* style);

Modified: trunk/MgDev/Common/Stylization/StylizationEngine.cpp
===================================================================
--- trunk/MgDev/Common/Stylization/StylizationEngine.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Common/Stylization/StylizationEngine.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -145,7 +145,9 @@
             try
             {
                 if (!reader->IsNull(gpName))
-                    reader->GetGeometry(gpName, lb, xformer);
+                {
+                    reader->GetGeometry(gpName, lb, xformer, se_renderer->GetDesiredPolygonOrientation());
+                }
                 else
                 {
                     // just move on to the next feature

Modified: trunk/MgDev/Doc/samples/ol2samples/wms/index.html
===================================================================
--- trunk/MgDev/Doc/samples/ol2samples/wms/index.html	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Doc/samples/ol2samples/wms/index.html	2019-10-06 12:18:03 UTC (rev 9630)
@@ -63,7 +63,7 @@
         <div id="main">
             <div class="container">
                 <div class="alert alert-info">
-                    <p>This example demonstrates a Map Definition being consumed as a <strong>XYZ layer</strong> via the MapGuide REST API with <a href="http://openlayers.org/">OpenLayers 3</a>.</p>
+                    <p>This example demonstrates a Map Definition being consumed as a <strong>XYZ layer</strong> with <a href="http://openlayers.org/">OpenLayers 3</a>.</p>
                     <p>An OpenStreetMap layer is included as a backdrop for context</p>
                     <strong>For maximum visual fidelity, your Map Definition should be in WGS84.PseudoMercator</strong>
                     <p>Sample not loading? <a href="../data/load.php">Check that the required resources have been loaded</a></p>

Modified: trunk/MgDev/Doc/samples/ol2samples/xyz/index_ol.html
===================================================================
--- trunk/MgDev/Doc/samples/ol2samples/xyz/index_ol.html	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Doc/samples/ol2samples/xyz/index_ol.html	2019-10-06 12:18:03 UTC (rev 9630)
@@ -57,7 +57,7 @@
         <div id="main">
             <div class="container">
                 <div class="alert alert-info">
-                    <p>This example demonstrates a Map Definition being consumed as a <strong>XYZ layer</strong> via the MapGuide REST API with <a href="http://openlayers.org/">OpenLayers 3</a>.</p>
+                    <p>This example demonstrates a Map Definition being consumed as a <strong>XYZ layer</strong> with <a href="http://openlayers.org/">OpenLayers 3</a>.</p>
                     <p>An OpenStreetMap layer is included as a backdrop for context</p>
                     <strong>For maximum visual fidelity, your Map Definition should be in WGS84.PseudoMercator</strong>
                     <p>Sample not loading? <a href="../data/load.php">Check that the required resources have been loaded</a></p>

Modified: trunk/MgDev/Doc/samples/samples.php
===================================================================
--- trunk/MgDev/Doc/samples/samples.php	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Doc/samples/samples.php	2019-10-06 12:18:03 UTC (rev 9630)
@@ -127,6 +127,7 @@
                     <li><a href="ol2samples/xyz/index_ol.html">Sheboygan map as XYZ layer</a></li>
                     <li><a href="ol2samples/utfgrid/index.html">Sheboygan map as XYZ layer with UTFGrid interaction tiles</a></li>
                     <li><a href="ol2samples/wms/index.html">Sheboygan map as WMS layers</a></li>
+                    <li><a href="ol2samples/mvt/index.html">Sheboygan map as Mapbox Vector Tiles</a></li>
                 </ul>
                 <? } ?>
             <? } ?>

Modified: trunk/MgDev/License.txt
===================================================================
--- trunk/MgDev/License.txt	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/License.txt	2019-10-06 12:18:03 UTC (rev 9630)
@@ -95,6 +95,69 @@
 
 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 
+MVT tile encoder copied from MVT driver for GDAL/OGR and adapted
+for use in MapGuide
+
+Files:
+  Common/Renderers/mvt/gpb.h
+  Common/Renderers/mvt/mvt_port.h (copied from cpl_port.h)
+  Common/Renderers/mvt/mvt_tile.h
+  Common/Renderers/mvt/mvt_tile.cpp
+
+Copyright (c) 1998, 2005, Frank Warmerdam
+Copyright (c) 2018, Even Rouault
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+UTFGrid encoder and PNG8 map quantization copied from MapServer and adapted
+for use in MapGuide
+
+Files:
+  Common/Renderers/MapUTFGrid.h
+  Common/Renderers/MapQuantization.h
+  Common/Renderers/MapQuantization.cpp
+
+Copyright (c) 1996-2005 Regents of the University of Minnesota.
+Copyright (c) 1996-2007 Regents of the University of Minnesota.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies of this Software or works derived from this Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
 Copyright and Licensing Information for ACE(TM)
 
 ACE(TM), TAO(TM), CIAO(TM), and CoSMIC(TM) (henceforth referred to as "DOC 

Modified: trunk/MgDev/Server/src/Services/Mapping/RSMgFeatureReader.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Mapping/RSMgFeatureReader.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/Services/Mapping/RSMgFeatureReader.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -22,6 +22,8 @@
 #include "LineBuffer.h"
 #include "ServerFeatureReader.h"
 #include "ServerGwsFeatureReader.h"
+#include "FdoGeometry.h"
+#include "FdoSpatial.h"
 
 //we want to catch the MgException and rethrow a FdoException which
 //the Stylizer knows how to catch and release. It does not know about
@@ -297,7 +299,8 @@
 
 LineBuffer* RSMgFeatureReader::GetGeometry(const wchar_t*   propertyName,
                                            LineBuffer*      lb,
-                                           CSysTransformer* xformer)
+                                           CSysTransformer* xformer,
+                                           RS_DesiredPolygonOrientation polygonOrientation)
 {
     RSFR_TRY()
 
@@ -311,8 +314,66 @@
         throw FdoException::Create(FdoException::NLSGetMessage(FDO_NLSID(FDO_60_NULL_POINTER)));
 
     _ASSERT(lb);
-    lb->LoadFromAgf(agf, sz, xformer);
 
+    //Peek at the raw AGF bytes to see if it's a polygon ahead of time.
+    int* ireader = (int*)agf;
+    // the geometry type
+    auto geomType = (FdoGeometryType)*ireader++;
+    bool isPolygon = geomType == FdoGeometryType_Polygon
+        || geomType == FdoGeometryType_MultiPolygon
+        || geomType == FdoGeometryType_CurvePolygon
+        || geomType == FdoGeometryType_MultiCurvePolygon;
+
+    if (isPolygon && polygonOrientation != RS_DesiredPolygonOrientation::NotApplicable)
+    {
+        FdoPtr<FdoFgfGeometryFactory> geomFactory = FdoFgfGeometryFactory::GetInstance();
+        FdoPtr<FdoIGeometry> geom = geomFactory->CreateGeometryFromFgf(agf, sz);
+        FdoPtr<FdoIGeometry> reoriented;
+        
+        switch (polygonOrientation)
+        {
+            case RS_DesiredPolygonOrientation::Clockwise:
+            {
+                reoriented = FdoSpatialUtility::FixPolygonVertexOrder(geom, FdoPolygonVertexOrderRule_CW);
+                FdoPtr<FdoByteArray> reFgf;
+                if (NULL != reoriented.p)
+                {
+                    reFgf = geomFactory->GetFgf(reoriented);
+                    lb->LoadFromAgf(reFgf->GetData(), reFgf->GetCount(), xformer);
+                }
+                else //Load original
+                {
+                    lb->LoadFromAgf(agf, sz, xformer);
+                }
+                break;
+            }
+            case RS_DesiredPolygonOrientation::CounterClockwise:
+            {
+                reoriented = FdoSpatialUtility::FixPolygonVertexOrder(geom, FdoPolygonVertexOrderRule_CCW);
+                FdoPtr<FdoByteArray> reFgf;
+                if (NULL != reoriented.p)
+                {
+                    reFgf = geomFactory->GetFgf(reoriented);
+                    lb->LoadFromAgf(reFgf->GetData(), reFgf->GetCount(), xformer);
+                }
+                else //Load original
+                {
+                    lb->LoadFromAgf(agf, sz, xformer);
+                }
+                break;
+            }
+            default:
+            {
+                _ASSERT(false);
+                break;
+            }
+        }
+    }
+    else
+    {
+        lb->LoadFromAgf(agf, sz, xformer);
+    }
+
     return lb;
 
     RSFR_CATCH()

Modified: trunk/MgDev/Server/src/Services/Mapping/RSMgFeatureReader.h
===================================================================
--- trunk/MgDev/Server/src/Services/Mapping/RSMgFeatureReader.h	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/Services/Mapping/RSMgFeatureReader.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -58,7 +58,7 @@
     virtual FdoInt32        GetInt32       (const wchar_t* propertyName);
     virtual FdoInt64        GetInt64       (const wchar_t* propertyName);
     virtual const wchar_t*  GetString      (const wchar_t* propertyName);
-    virtual LineBuffer*     GetGeometry    (const wchar_t* propertyName, LineBuffer* lb, CSysTransformer* xformer);
+    virtual LineBuffer*     GetGeometry    (const wchar_t* propertyName, LineBuffer* lb, CSysTransformer* xformer, RS_DesiredPolygonOrientation polygonOrientation);
     virtual RS_Raster*      GetRaster      (const wchar_t* propertyName);
     virtual const wchar_t*  GetAsString    (const wchar_t* propertyName);
     virtual RS_InputStream* GetBLOB        (const wchar_t* propertyName);

Modified: trunk/MgDev/Server/src/Services/Mapping/ServerMappingService.vcxproj
===================================================================
--- trunk/MgDev/Server/src/Services/Mapping/ServerMappingService.vcxproj	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/Services/Mapping/ServerMappingService.vcxproj	2019-10-06 12:18:03 UTC (rev 9630)
@@ -104,10 +104,10 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
-      <AdditionalDependencies>ACEd.lib;xerces-c_3mgD.lib;FDO.lib;FDOCommon.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>ACEd.lib;xerces-c_3mgD.lib;FDO.lib;FDOCommon.lib;FDOGeometry.lib;FDOSpatial.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>$(OutDir)MgServerMappingServiced.dll</OutputFile>
       <AdditionalLibraryDirectories>..\..\..\..\Oem\ACE\ACE_wrappers\lib\$(Configuration);..\..\..\..\Oem\dbxml\lib\$(Configuration);..\..\..\..\Oem\FDO\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
-      <DelayLoadDLLs>FDO.dll;FDOCommon.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
+      <DelayLoadDLLs>FDO.dll;FDOCommon.dll;FDOGeometry.dll;FDOSpatial.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ProgramDatabaseFile>$(OutDir)MgServerMappingServiced.pdb</ProgramDatabaseFile>
       <SubSystem>Windows</SubSystem>
@@ -131,10 +131,10 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
-      <AdditionalDependencies>ACEd.lib;xerces-c_3mgD.lib;FDO.lib;FDOCommon.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>ACEd.lib;xerces-c_3mgD.lib;FDO.lib;FDOCommon.lib;FDOGeometry.lib;FDOSpatial.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>$(OutDir)MgServerMappingServiced.dll</OutputFile>
       <AdditionalLibraryDirectories>..\..\..\..\Oem\ACE\ACE_wrappers\lib64\$(Configuration);..\..\..\..\Oem\dbxml\lib64\$(Configuration);..\..\..\..\Oem\FDO\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
-      <DelayLoadDLLs>FDO.dll;FDOCommon.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
+      <DelayLoadDLLs>FDO.dll;FDOCommon.dll;FDOGeometry.dll;FDOSpatial.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ProgramDatabaseFile>$(OutDir)MgServerMappingServiced.pdb</ProgramDatabaseFile>
       <SubSystem>Windows</SubSystem>
@@ -157,10 +157,10 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
-      <AdditionalDependencies>ACE.lib;xerces-c_3mg.lib;FDO.lib;FDOCommon.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>ACE.lib;xerces-c_3mg.lib;FDO.lib;FDOCommon.lib;FDOGeometry.lib;FDOSpatial.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>$(OutDir)MgServerMappingService.dll</OutputFile>
       <AdditionalLibraryDirectories>..\..\..\..\Oem\ACE\ACE_wrappers\lib\$(Configuration);..\..\..\..\Oem\dbxml\lib\$(Configuration);..\..\..\..\Oem\FDO\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
-      <DelayLoadDLLs>FDO.dll;FDOCommon.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
+      <DelayLoadDLLs>FDO.dll;FDOCommon.dll;FDOGeometry.dll;FDOSpatial.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ProgramDatabaseFile>$(OutDir)MgServerMappingService.pdb</ProgramDatabaseFile>
       <SubSystem>Windows</SubSystem>
@@ -185,10 +185,10 @@
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
     </ClCompile>
     <Link>
-      <AdditionalDependencies>ACE.lib;xerces-c_3mg.lib;FDO.lib;FDOCommon.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>ACE.lib;xerces-c_3mg.lib;FDO.lib;FDOCommon.lib;FDOGeometry.lib;FDOSpatial.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <OutputFile>$(OutDir)MgServerMappingService.dll</OutputFile>
       <AdditionalLibraryDirectories>..\..\..\..\Oem\ACE\ACE_wrappers\lib64\$(Configuration);..\..\..\..\Oem\dbxml\lib64\$(Configuration);..\..\..\..\Oem\FDO\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
-      <DelayLoadDLLs>FDO.dll;FDOCommon.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
+      <DelayLoadDLLs>FDO.dll;FDOCommon.dll;FDOGeometry.dll;FDOSpatial.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ProgramDatabaseFile>$(OutDir)MgServerMappingService.pdb</ProgramDatabaseFile>
       <SubSystem>Windows</SubSystem>

Copied: trunk/MgDev/Server/src/Services/Rendering/OpRenderTileMVT.cpp (from rev 9629, sandbox/jng/mvt_alt/Server/src/Services/Rendering/OpRenderTileMVT.cpp)
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/OpRenderTileMVT.cpp	                        (rev 0)
+++ trunk/MgDev/Server/src/Services/Rendering/OpRenderTileMVT.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -0,0 +1,111 @@
+//
+//  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 "OpRenderTileMVT.h"
+#include "LogManager.h"
+
+MgOpRenderTileMVT::MgOpRenderTileMVT()
+{
+}
+
+MgOpRenderTileMVT::~MgOpRenderTileMVT()
+{
+}
+
+void MgOpRenderTileMVT::Execute()
+{
+    ACE_DEBUG((LM_DEBUG, ACE_TEXT("  (%t) MgOpRenderTileMVT::Execute()\n")));
+
+    MG_LOG_OPERATION_MESSAGE(L"MgOpRenderTileMVT");
+
+    MG_TRY()
+
+        MG_LOG_OPERATION_MESSAGE_INIT(m_packet.m_OperationVersion, m_packet.m_NumArguments);
+
+    ACE_ASSERT(m_stream != NULL);
+
+    if (6 == 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);
+
+        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_PARAMETERS_END();
+
+        Validate();
+
+        Ptr<MgByteReader> byteReader = m_service->RenderTileMVT(map, baseMapLayerGroupName, x, y, z, tileDpi);
+
+        EndExecution(byteReader);
+    }
+    else
+    {
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_START();
+        MG_LOG_OPERATION_MESSAGE_PARAMETERS_END();
+    }
+
+    if (!m_argsRead)
+    {
+        throw new MgOperationProcessingException(L"MgOpRenderTileMVT.Execute",
+            __LINE__, __WFILE__, NULL, L"", NULL);
+    }
+
+    // Successful operation
+    MG_LOG_OPERATION_MESSAGE_ADD_STRING(MgResources::Success.c_str());
+
+    MG_CATCH(L"MgOpRenderTileMVT.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/OpRenderTileMVT.h (from rev 9629, sandbox/jng/mvt_alt/Server/src/Services/Rendering/OpRenderTileMVT.h)
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/OpRenderTileMVT.h	                        (rev 0)
+++ trunk/MgDev/Server/src/Services/Rendering/OpRenderTileMVT.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -0,0 +1,33 @@
+//
+//  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_MVT_H
+#define MG_OP_RENDER_TILE_MVT_H
+
+#include "RenderingOperation.h"
+
+class MgOpRenderTileMVT : public MgRenderingOperation
+{
+public:
+    MgOpRenderTileMVT();
+    virtual ~MgOpRenderTileMVT();
+
+public:
+    virtual void Execute();
+};
+
+#endif
\ No newline at end of file

Modified: trunk/MgDev/Server/src/Services/Rendering/RenderingOperationFactory.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/RenderingOperationFactory.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/Services/Rendering/RenderingOperationFactory.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -153,7 +153,17 @@
                 L"MgRenderingOperationFactory.GetOperation", __LINE__, __WFILE__, NULL, L"", NULL);
         }
         break;
-
+    case MgRenderingServiceOpId::RenderTileMVT:
+        switch (VERSION_NO_PHASE(operationVersion))
+        {
+        case VERSION_SUPPORTED(4, 0):
+            handler.reset(new MgOpRenderTileMVT());
+            break;
+        default:
+            throw new MgInvalidOperationVersionException(
+                L"MgRenderingOperationFactory.GetOperation", __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+        break;
     case MgRenderingServiceOpId::RenderDynamicOverlay:
         switch (VERSION_NO_PHASE(operationVersion))
         {

Modified: trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -20,6 +20,7 @@
 #include "DefaultStylizer.h"
 #include "AGGRenderer.h"
 #include "UTFGridRenderer.h"
+#include "MVTRenderer.h"
 #include "RSMgSymbolManager.h"
 #include "RSMgFeatureReader.h"
 #include "FeatureInfoRenderer.h"
@@ -38,6 +39,9 @@
 #define XYZ_TILE_WIDTH 256
 #define XYZ_TILE_HEIGHT 256
 
+#define MVT_TILE_WIDTH 4096
+#define MVT_TILE_HEIGHT 4096
+
 // the maximum number of allowed pixels for rendered images
 static const INT32 MAX_PIXELS = 16384*16384;
 static const INT32 FILTER_VISIBLE = 1;
@@ -204,7 +208,7 @@
     return ret.Detach();
 }
 
-void MgServerRenderingService::ComputeXYZTileExtents(MgMap* map, INT32 x, INT32 y, INT32 z, RS_Bounds& extent)
+void MgServerRenderingService::ComputeXYZTileExtents(MgCoordinateSystem* mapCs, INT32 x, INT32 y, INT32 z, RS_Bounds& extent)
 {
     //XYZ to lat/lon math. From this we can convert to the bounds in the map's CS
     //
@@ -221,11 +225,9 @@
     double mcsMaxX = std::max(lonMin, lonMax);
     double mcsMaxY = std::max(latMin, latMax);
 
-    STRING mapCsWkt = map->GetMapSRS();
-    Ptr<MgCoordinateSystemFactory> csFactory = new MgCoordinateSystemFactory();
-    Ptr<MgCoordinateSystem> mapCs = csFactory->Create(mapCsWkt);
     if (mapCs->GetCsCode() != L"LL84")
     {
+        Ptr<MgCoordinateSystemFactory> csFactory = new MgCoordinateSystemFactory();
         //Set up LL to map transform and transform the bounds into map space
         Ptr<MgCoordinateSystem> llCs = csFactory->CreateFromCode(L"LL84");
         Ptr<MgCoordinateSystemTransform> trans = csFactory->GetTransform(llCs, mapCs);
@@ -246,6 +248,14 @@
     extent.maxy = mcsMaxY;
 }
 
+void MgServerRenderingService::ComputeXYZTileExtents(STRING mapCsWkt, INT32 x, INT32 y, INT32 z, RS_Bounds& extent)
+{
+    Ptr<MgCoordinateSystemFactory> csFactory = new MgCoordinateSystemFactory();
+    Ptr<MgCoordinateSystem> mapCs = csFactory->Create(mapCsWkt);
+    
+    ComputeXYZTileExtents(mapCs, x, y, z, extent);
+}
+
 MgByteReader* MgServerRenderingService::RenderTileXYZ(MgMap* map,
                                                       CREFSTRING baseMapLayerGroupName,
                                                       INT32 x,
@@ -313,7 +323,7 @@
 
     // get map extent that corresponds to tile extent
     RS_Bounds extent;
-    ComputeXYZTileExtents(map, x, y, z, extent);
+    ComputeXYZTileExtents(map->GetMapSRS(), x, y, z, extent);
 
     // use the map's background color, but always make it fully transparent
     RS_Color bgColor;
@@ -339,7 +349,7 @@
 
     // 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);
+        throw new MgOutOfRangeException(L"MgServerRenderingService.RenderTileUTFGrid", __LINE__, __WFILE__, NULL, L"MgInvalidImageSizeTooBig", NULL);
 
     // create a temporary collection containing all the layers for the base group
     Ptr<MgLayerCollection> layers = map->GetLayers();
@@ -428,6 +438,130 @@
     return ret.Detach();
 }
 
+MgByteReader* MgServerRenderingService::RenderTileMVT(MgMap * map, 
+                                                      CREFSTRING baseMapLayerGroupName, 
+                                                      INT32 x,
+                                                      INT32 y, 
+                                                      INT32 z, 
+                                                      INT32 dpi)
+{
+    Ptr<MgByteReader> ret;
+
+    MG_TRY()
+
+    CHECKARGUMENTNULL(map, L"MgServerRenderingService.RenderTileMVT");
+    CHECKARGUMENTEMPTYSTRING(baseMapLayerGroupName, L"MgServerRenderingService.RenderTileMVT");
+
+    // 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.RenderTileMVT",
+            __LINE__, __WFILE__, &arguments, L"MgMapLayerGroupNameNotFound", NULL);
+    }
+
+    //Set the dpi
+    map->SetDisplayDpi(dpi);
+
+    int width = MVT_TILE_WIDTH;
+    int height = MVT_TILE_HEIGHT;
+
+    // MVT tiles are always written in web mercator
+    STRING srs = m_pCSFactory->ConvertCoordinateSystemCodeToWkt(L"WGS84.PseudoMercator");
+    Ptr<MgCoordinateSystem> dstCs = m_pCSFactory->Create(srs);
+    double metersPerUnit = dstCs->ConvertCoordinateSystemUnitsToMeters(1.0);
+
+    // Inlining same logic from RenderTile() overload below as we want the same logic, but we want to pass scale
+    // instead of scale index
+
+    // get map extent that corresponds to tile extent
+    RS_Bounds extent;
+    ComputeXYZTileExtents(dstCs, x, y, z, extent);
+
+    INT32 drawWidth = width;
+    INT32 drawHeight = height;
+    double scale = 0.0;
+    ComputeScaledDimensions(extent, width, height, dpi, metersPerUnit, 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());
+
+    // 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);
+
+    // We'd like to re-use RenderMapInternal, but its design is biased towards image-based SE_Renderers (that expect to
+    // save to some RS_ByteData that an MgByteReader is returned from.
+    //
+    // Our MVTRenderer is not such a renderer, so we have to inline the pertinent bits here
+    
+    // set the map scale to the requested scale
+    map->SetViewScale(scale);
+
+    RS_String units = dstCs.p ? dstCs->GetUnits() : L"";
+
+    // get the session ID
+    STRING sessionId;
+    Ptr<MgUserInformation> userInfo = MgUserInformation::GetCurrentUserInfo();
+    if (userInfo != NULL)
+        sessionId = userInfo->GetMgSessionId();
+
+    MVTRenderer dr(x, y, z);
+
+    RSMgSymbolManager mgr(m_svcResource);
+    dr.SetSymbolManager(&mgr);
+
+    SEMgSymbolManager semgr(m_svcResource);
+    DefaultStylizer ds(&semgr);
+
+    RS_Color bgcolor(0, 0, 0, 255); //Not used by MVTRenderer
+
+    Ptr<MgPoint> ptCenter = map->GetViewCenter();
+    Ptr<MgCoordinate> coord = ptCenter->GetCoordinate();
+    RS_MapUIInfo mapInfo(sessionId, map->GetName(), map->GetObjectId(), srs, units, bgcolor, coord->GetX(), coord->GetY(), scale);
+
+    // begin map stylization
+    dr.StartMap(&mapInfo, extent, scale, map->GetDisplayDpi(), map->GetMetersPerUnit(), NULL);
+
+    // We can't use RenderMapInternal, but we can use RenderLayers, which is all we really need
+    RenderLayers(map, roLayers, &ds, &dr, dstCs, true, scale, L"MVT", NULL);
+
+    dr.EndMap();
+
+    // restore the base group's visibility
+    baseGroup->SetVisible(groupVisible);
+
+    // Now extract the encoded MVT tile and pack it into a MgByteReader
+    size_t size;
+    auto buf = dr.GetMVTContent(size);
+    Ptr<MgByte> mvtBytes = new MgByte(buf, size);
+    dr.ReleaseMVTContent(buf);
+    Ptr<MgByteSource> bs = new MgByteSource(mvtBytes);
+
+    bs->SetMimeType(MgMimeType::Mvt);
+
+    ret = bs->GetReader();
+
+    MG_CATCH_AND_THROW(L"MgServerRenderingService.RenderTileMVT")
+
+    return ret.Detach();
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 /// render a map using all layers from the baseGroup
 MgByteReader* MgServerRenderingService::RenderTileInternal(MgMap* map,
@@ -2405,7 +2539,8 @@
     // 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);
+    STRING mapSrs = map->GetMapSRS();
+    ComputeXYZTileExtents(mapSrs, x, y, z, extent);
 
     // If meta-tiling, request bounds for:
     //   (x, y, z) -> (x + (tm - 1), y + (tm - 1), z)
@@ -2415,7 +2550,7 @@
         RS_Bounds extent2;
         INT32 endX = x + (metaTilingFactor - 1);
         INT32 endY = y + (metaTilingFactor - 1);
-        ComputeXYZTileExtents(map, endX, endY, z, extent2);
+        ComputeXYZTileExtents(mapSrs, endX, endY, z, extent2);
 
         extent.add_bounds(extent2);
     }

Modified: trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.h
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.h	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -100,6 +100,13 @@
                                             INT32 z,
                                             INT32 dpi);
 
+    virtual MgByteReader* RenderTileMVT(MgMap* map,
+                                        CREFSTRING baseMapLayerGroupName,
+                                        INT32 x,
+                                        INT32 y,
+                                        INT32 z,
+                                        INT32 dpi);
+
     virtual MgByteReader* RenderDynamicOverlay(MgMap* map,
                                                MgSelection* selection,
                                                CREFSTRING format);
@@ -304,7 +311,8 @@
     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);
+    static void ComputeXYZTileExtents(MgCoordinateSystem* mapCs, INT32 x, INT32 y, INT32 z, RS_Bounds& extent);
+    static void ComputeXYZTileExtents(STRING mapCsWkt, INT32 x, INT32 y, INT32 z, RS_Bounds& extent);
     // used for tile generation
     MgByteReader* RenderTileInternal(MgMap* map,
                                      MgLayerGroup* baseGroup,

Modified: trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.vcxproj
===================================================================
--- trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.vcxproj	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.vcxproj	2019-10-06 12:18:03 UTC (rev 9630)
@@ -252,6 +252,12 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="OpRenderTileMVT.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>
@@ -318,6 +324,7 @@
     <ClInclude Include="OpRenderMetatileXYZ.h" />
     <ClInclude Include="OpRenderTile.h" />
     <ClInclude Include="OpRenderTileFromMetatile.h" />
+    <ClInclude Include="OpRenderTileMVT.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-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/Services/Rendering/ServerRenderingService.vcxproj.filters	2019-10-06 12:18:03 UTC (rev 9630)
@@ -51,6 +51,9 @@
     <ClCompile Include="OpRenderTileFromMetatile.cpp">
       <Filter>Ops</Filter>
     </ClCompile>
+    <ClCompile Include="OpRenderTileMVT.cpp">
+      <Filter>Ops</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="OpQueryFeatureProperties.h">
@@ -98,6 +101,9 @@
     <ClInclude Include="OpRenderTileFromMetatile.h">
       <Filter>Ops</Filter>
     </ClInclude>
+    <ClInclude Include="OpRenderTileMVT.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-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/Services/Rendering/ServerRenderingServiceBuild.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -29,6 +29,7 @@
 #include "OpRenderTile.cpp"
 #include "OpRenderTileXYZ.cpp"
 #include "OpRenderTileUTFGrid.cpp"
+#include "OpRenderTileMVT.cpp"
 #include "OpQueryFeatures.cpp"
 #include "OpQueryFeatureProperties.cpp"
 #include "RenderingOperation.cpp"

Modified: trunk/MgDev/Server/src/Services/Tile/ServerTileService.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Tile/ServerTileService.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/Services/Tile/ServerTileService.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -410,6 +410,7 @@
         xml.append("<Value>JPG</Value>");
         xml.append("<Value>GIF</Value>");
         xml.append("<Value>UTFGRID</Value>");
+        xml.append("<Value>MVT</Value>");
         xml.append("</ConnectionProperty>\n");
 
         //Property: RetinaScale

Modified: trunk/MgDev/Server/src/Services/Tile/TileCacheXYZProvider.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Tile/TileCacheXYZProvider.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/Services/Tile/TileCacheXYZProvider.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -46,7 +46,7 @@
 
 bool MgTileCacheXYZProvider::IsTileImageFormat()
 {
-    return m_format == L"UTFGRID";
+    return m_format != L"UTFGRID" && m_format != L"MVT";
 }
 
 MgByteReader* MgTileCacheXYZProvider::GetTile(CREFSTRING baseMapLayerGroupName,
@@ -83,9 +83,14 @@
     if (svcRendering != NULL)
     {
         // Test for non-image formats first
-        if (m_format == L"UTFGRID")
+        if (m_format == L"MVT")
         {
             // generate the tile
+            img = svcRendering->RenderTileMVT(map, baseMapLayerGroupName, tileRow, tileColumn, scaleIndex, map->GetDisplayDpi());
+        }
+        else if (m_format == L"UTFGRID")
+        {
+            // generate the tile
             img = svcRendering->RenderTileUTFGrid(map, baseMapLayerGroupName, tileRow, tileColumn, scaleIndex, map->GetDisplayDpi());
         }
         else //Assume it must be image-based at this point
@@ -111,11 +116,15 @@
 
 INT32 MgTileCacheXYZProvider::GetDefaultTileSizeX()
 {
+    if (m_format == L"MVT")
+        return 4096;
     return 256 * m_retinaScale;
 }
 
 INT32 MgTileCacheXYZProvider::GetDefaultTileSizeY()
 {
+    if (m_format == L"MVT")
+        return 4096;
     return 256 * m_retinaScale;
 }
 
@@ -143,6 +152,10 @@
     {
         return L"utfgrid";
     }
+    else if (m_format == L"MVT")
+    {
+        return L"mvt";
+    }
     else
     {
         return L"png";

Modified: trunk/MgDev/Server/src/UnitTesting/CMakeLists.txt
===================================================================
--- trunk/MgDev/Server/src/UnitTesting/CMakeLists.txt	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/UnitTesting/CMakeLists.txt	2019-10-06 12:18:03 UTC (rev 9630)
@@ -7,6 +7,7 @@
     ${MG_COMMON_DIR}/MapGuideCommon
     ${MG_COMMON_DIR}/MdfModel
     ${MG_COMMON_DIR}/MdfParser
+    ${MG_COMMON_DIR}/Renderers
     ${MG_COMMON_DIR}/Stylization
     ${MG_SERVER_COMMON_DIR}
     ${MG_SERVER_COMMON_DIR}/Manager

Modified: trunk/MgDev/Server/src/UnitTesting/TestMisc.cpp
===================================================================
--- trunk/MgDev/Server/src/UnitTesting/TestMisc.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/UnitTesting/TestMisc.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -23,6 +23,8 @@
 #include "../UnitTesting/CppUnitExtensions.h"
 #include "FoundationDefs.h"
 #include "FdoConnectionManager.h"
+#include "LineBuffer.h"
+#include "RS_BufferOutputStream.h"
 
 const STRING TEST_LOCALE = L"en";
 static const INT32 MG_TEST_THREADS = 16;
@@ -794,7 +796,7 @@
     GetConfigValueThreadData* threadData = (GetConfigValueThreadData*)param;
     INT32 threadId = threadData->threadId;
 #ifdef _DEBUG
-    printf("> thread %d started, tile %d,%d\n", threadId, tileRow, tileCol);
+    printf("> thread %d started\n", threadId);
 #endif
 
     try
@@ -912,4 +914,519 @@
     {
         throw;
     }
+}
+
+void TestMisc::TestCase_LineBuffer_Conversion()
+{
+    //This isn't really a test, it's more verification and internal documentation
+    //of how LineBuffers are meant to be represented for various geometry types
+    try
+    {
+        MgWktReaderWriter wktRw;
+        MgAgfReaderWriter agfRw;
+        Ptr<MgGeometry> gPoint = wktRw.Read(L"POINT (30 10)");
+        Ptr<MgGeometry> gMultiPoint = wktRw.Read(L"MULTIPOINT (10 40, 40 30, 20 20, 30 10)");
+        Ptr<MgGeometry> gLineStr = wktRw.Read(L"LINESTRING (30 10, 10 30, 40 40)");
+        Ptr<MgGeometry> gMultiLineString = wktRw.Read(L"MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))");
+        Ptr<MgGeometry> gPolygon1 = wktRw.Read(L"POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))");
+        Ptr<MgGeometry> gPolygon2 = wktRw.Read(L"POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))");
+        STRING mpWkt1 = L"MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))";
+        Ptr<MgGeometry> gMultiPolygon1 = wktRw.Read(mpWkt1);
+        STRING mpWkt2 = L"MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20)))";
+        Ptr<MgGeometry> gMultiPolygon2 = wktRw.Read(mpWkt2);
+
+        //Point
+        {
+            LineBuffer lb(8);
+            Ptr<MgByteReader> agf = agfRw.Write(gPoint);
+            Ptr<MgByteSink> agfSink = new MgByteSink(agf);
+            Ptr<MgByte> bytes = agfSink->ToBuffer();
+            lb.LoadFromAgf(bytes->Bytes(), bytes->GetLength(), nullptr);
+
+            CPPUNIT_ASSERT(1 == lb.geom_count());
+            CPPUNIT_ASSERT(1 == lb.point_count());
+            CPPUNIT_ASSERT(30 == lb.x_coord(0));
+            CPPUNIT_ASSERT(10 == lb.y_coord(0));
+        }
+
+        //MultiPoint
+        {
+            LineBuffer lb(8);
+            Ptr<MgByteReader> agf = agfRw.Write(gMultiPoint);
+            Ptr<MgByteSink> agfSink = new MgByteSink(agf);
+            Ptr<MgByte> bytes = agfSink->ToBuffer();
+            lb.LoadFromAgf(bytes->Bytes(), bytes->GetLength(), nullptr);
+
+            CPPUNIT_ASSERT(4 == lb.geom_count());
+            CPPUNIT_ASSERT(4 == lb.point_count());
+            CPPUNIT_ASSERT(10 == lb.x_coord(0));
+            CPPUNIT_ASSERT(40 == lb.y_coord(0));
+            CPPUNIT_ASSERT(40 == lb.x_coord(1));
+            CPPUNIT_ASSERT(30 == lb.y_coord(1));
+            CPPUNIT_ASSERT(20 == lb.x_coord(2));
+            CPPUNIT_ASSERT(20 == lb.y_coord(2));
+            CPPUNIT_ASSERT(30 == lb.x_coord(3));
+            CPPUNIT_ASSERT(10 == lb.y_coord(3));
+        }
+
+        //LineString
+        {
+            LineBuffer lb(8);
+            Ptr<MgByteReader> agf = agfRw.Write(gLineStr);
+            Ptr<MgByteSink> agfSink = new MgByteSink(agf);
+            Ptr<MgByte> bytes = agfSink->ToBuffer();
+            lb.LoadFromAgf(bytes->Bytes(), bytes->GetLength(), nullptr);
+
+            int lineIdx = 0;
+            CPPUNIT_ASSERT(1 == lb.geom_count());
+            CPPUNIT_ASSERT(1 == lb.cntr_count());
+            CPPUNIT_ASSERT(3 == lb.cntr_size(lineIdx));
+
+            auto ptOffset = lb.contour_start_point(lineIdx);
+            for (int i = 0; i < lb.cntr_size(lineIdx); i++)
+            {
+                auto x = lb.x_coord(ptOffset + i);
+                auto y = lb.y_coord(ptOffset + i);
+                switch (i)
+                {
+                case 0:
+                    CPPUNIT_ASSERT(30 == x);
+                    CPPUNIT_ASSERT(10 == y);
+                    break;
+                case 1:
+                    CPPUNIT_ASSERT(10 == x);
+                    CPPUNIT_ASSERT(30 == y);
+                    break;
+                case 2:
+                    CPPUNIT_ASSERT(40 == x);
+                    CPPUNIT_ASSERT(40 == y);
+                    break;
+                }
+            }
+        }
+        
+        //MultiLineString
+        {
+            LineBuffer lb(8);
+            Ptr<MgByteReader> agf = agfRw.Write(gMultiLineString);
+            Ptr<MgByteSink> agfSink = new MgByteSink(agf);
+            Ptr<MgByte> bytes = agfSink->ToBuffer();
+            lb.LoadFromAgf(bytes->Bytes(), bytes->GetLength(), nullptr);
+
+            CPPUNIT_ASSERT(2 == lb.geom_count());
+            CPPUNIT_ASSERT(1 == lb.geom_size(0));
+            CPPUNIT_ASSERT(1 == lb.geom_size(1));
+            CPPUNIT_ASSERT(2 == lb.cntr_count());
+            CPPUNIT_ASSERT(3 == lb.cntr_size(0));
+            CPPUNIT_ASSERT(4 == lb.cntr_size(1));
+
+            //LineString 1
+            {
+                auto ptOffset = lb.contour_start_point(0);
+                for (int i = 0; i < lb.cntr_size(0); i++)
+                {
+                    auto x = lb.x_coord(ptOffset + i);
+                    auto y = lb.y_coord(ptOffset + i);
+                    switch (i)
+                    {
+                    case 0:
+                        CPPUNIT_ASSERT(10 == x);
+                        CPPUNIT_ASSERT(10 == y);
+                        break;
+                    case 1:
+                        CPPUNIT_ASSERT(20 == x);
+                        CPPUNIT_ASSERT(20 == y);
+                        break;
+                    case 2:
+                        CPPUNIT_ASSERT(10 == x);
+                        CPPUNIT_ASSERT(40 == y);
+                        break;
+                    }
+                }
+            }
+
+            //LineString 2
+            {
+                auto ptOffset = lb.contour_start_point(1);
+                for (int i = 0; i < lb.cntr_size(1); i++)
+                {
+                    auto x = lb.x_coord(ptOffset + i);
+                    auto y = lb.y_coord(ptOffset + i);
+                    switch (i)
+                    {
+                    case 0:
+                        CPPUNIT_ASSERT(40 == x);
+                        CPPUNIT_ASSERT(40 == y);
+                        break;
+                    case 1:
+                        CPPUNIT_ASSERT(30 == x);
+                        CPPUNIT_ASSERT(30 == y);
+                        break;
+                    case 2:
+                        CPPUNIT_ASSERT(40 == x);
+                        CPPUNIT_ASSERT(20 == y);
+                        break;
+                    case 3:
+                        CPPUNIT_ASSERT(30 == x);
+                        CPPUNIT_ASSERT(10 == y);
+                        break;
+                    }
+                }
+            }
+        }
+    
+        //Polygon 1 (1 outer, 0 inner)
+        {
+            LineBuffer lb(8);
+            Ptr<MgByteReader> agf = agfRw.Write(gPolygon1);
+            Ptr<MgByteSink> agfSink = new MgByteSink(agf);
+            Ptr<MgByte> bytes = agfSink->ToBuffer();
+            lb.LoadFromAgf(bytes->Bytes(), bytes->GetLength(), nullptr);
+
+            CPPUNIT_ASSERT(1 == lb.geom_count());
+            CPPUNIT_ASSERT(1 == lb.geom_size(0));
+            auto cc = lb.cntr_count();
+            CPPUNIT_ASSERT(1 == cc);
+
+            int lineIdx = 0;
+            CPPUNIT_ASSERT(5 == lb.cntr_size(lineIdx));
+
+            auto ptOffset = lb.contour_start_point(lineIdx);
+            for (int i = 0; i < lb.cntr_size(lineIdx); i++)
+            {
+                auto x = lb.x_coord(ptOffset + i);
+                auto y = lb.y_coord(ptOffset + i);
+                switch (i)
+                {
+                case 0:
+                    CPPUNIT_ASSERT(30 == x);
+                    CPPUNIT_ASSERT(10 == y);
+                    break;
+                case 1:
+                    CPPUNIT_ASSERT(40 == x);
+                    CPPUNIT_ASSERT(40 == y);
+                    break;
+                case 2:
+                    CPPUNIT_ASSERT(20 == x);
+                    CPPUNIT_ASSERT(40 == y);
+                    break;
+                case 3:
+                    CPPUNIT_ASSERT(10 == x);
+                    CPPUNIT_ASSERT(20 == y);
+                    break;
+                case 4:
+                    CPPUNIT_ASSERT(30 == x);
+                    CPPUNIT_ASSERT(10 == y);
+                    break;
+                }
+            }
+        }
+
+        //Polygon 2 (1 outer, 1 inner)
+        {
+            LineBuffer lb(8);
+            Ptr<MgByteReader> agf = agfRw.Write(gPolygon2);
+            Ptr<MgByteSink> agfSink = new MgByteSink(agf);
+            Ptr<MgByte> bytes = agfSink->ToBuffer();
+            lb.LoadFromAgf(bytes->Bytes(), bytes->GetLength(), nullptr);
+
+            CPPUNIT_ASSERT(1 == lb.geom_count());
+            CPPUNIT_ASSERT(2 == lb.geom_size(0));
+            auto cc = lb.cntr_count();
+            CPPUNIT_ASSERT(2 == cc);
+
+            //Outer Ring
+            {
+                CPPUNIT_ASSERT(5 == lb.cntr_size(0));
+                auto ptOffset = lb.contour_start_point(0);
+                for (int i = 0; i < lb.cntr_size(0); i++)
+                {
+                    auto x = lb.x_coord(ptOffset + i);
+                    auto y = lb.y_coord(ptOffset + i);
+                    switch (i)
+                    {
+                    case 0:
+                        CPPUNIT_ASSERT(35 == x);
+                        CPPUNIT_ASSERT(10 == y);
+                        break;
+                    case 1:
+                        CPPUNIT_ASSERT(45 == x);
+                        CPPUNIT_ASSERT(45 == y);
+                        break;
+                    case 2:
+                        CPPUNIT_ASSERT(15 == x);
+                        CPPUNIT_ASSERT(40 == y);
+                        break;
+                    case 3:
+                        CPPUNIT_ASSERT(10 == x);
+                        CPPUNIT_ASSERT(20 == y);
+                        break;
+                    case 4:
+                        CPPUNIT_ASSERT(35 == x);
+                        CPPUNIT_ASSERT(10 == y);
+                        break;
+                    }
+                }
+            }
+
+            //Inner Ring
+            {
+                CPPUNIT_ASSERT(4 == lb.cntr_size(1));
+                auto ptOffset = lb.contour_start_point(1);
+                for (int i = 0; i < lb.cntr_size(1); i++)
+                {
+                    auto x = lb.x_coord(ptOffset + i);
+                    auto y = lb.y_coord(ptOffset + i);
+                    switch (i)
+                    {
+                    case 0:
+                        CPPUNIT_ASSERT(20 == x);
+                        CPPUNIT_ASSERT(30 == y);
+                        break;
+                    case 1:
+                        CPPUNIT_ASSERT(35 == x);
+                        CPPUNIT_ASSERT(35 == y);
+                        break;
+                    case 2:
+                        CPPUNIT_ASSERT(30 == x);
+                        CPPUNIT_ASSERT(20 == y);
+                        break;
+                    case 3:
+                        CPPUNIT_ASSERT(20 == x);
+                        CPPUNIT_ASSERT(30 == y);
+                        break;
+                    }
+                }
+            }
+        }
+
+        //MultiPolygon 1
+        {
+            LineBuffer lb(8);
+            Ptr<MgByteReader> agf = agfRw.Write(gMultiPolygon1);
+            Ptr<MgByteSink> agfSink = new MgByteSink(agf);
+            Ptr<MgByte> bytes = agfSink->ToBuffer();
+            lb.LoadFromAgf(bytes->Bytes(), bytes->GetLength(), nullptr);
+
+            CPPUNIT_ASSERT(2 == lb.geom_count());
+            CPPUNIT_ASSERT(1 == lb.geom_size(0));
+            CPPUNIT_ASSERT(1 == lb.geom_size(1));
+            auto cc = lb.cntr_count();
+            CPPUNIT_ASSERT(2 == cc);
+            auto closed1 = lb.contour_closed(0);
+            auto closed2 = lb.contour_closed(1);
+
+            //Polygon 1
+            {
+                CPPUNIT_ASSERT(4 == lb.cntr_size(0));
+                auto ptOffset = lb.contour_start_point(0);
+                for (int i = 0; i < lb.cntr_size(0); i++)
+                {
+                    auto x = lb.x_coord(ptOffset + i);
+                    auto y = lb.y_coord(ptOffset + i);
+                    switch (i)
+                    {
+                    case 0:
+                        CPPUNIT_ASSERT(30 == x);
+                        CPPUNIT_ASSERT(20 == y);
+                        break;
+                    case 1:
+                        CPPUNIT_ASSERT(45 == x);
+                        CPPUNIT_ASSERT(40 == y);
+                        break;
+                    case 2:
+                        CPPUNIT_ASSERT(10 == x);
+                        CPPUNIT_ASSERT(40 == y);
+                        break;
+                    case 3:
+                        CPPUNIT_ASSERT(30 == x);
+                        CPPUNIT_ASSERT(20 == y);
+                        break;
+                    }
+                }
+            }
+
+            //Polygon 2
+            {
+                CPPUNIT_ASSERT(5 == lb.cntr_size(1));
+                auto ptOffset = lb.contour_start_point(1);
+                for (int i = 0; i < lb.cntr_size(1); i++)
+                {
+                    auto x = lb.x_coord(ptOffset + i);
+                    auto y = lb.y_coord(ptOffset + i);
+                    switch (i)
+                    {
+                    case 0:
+                        CPPUNIT_ASSERT(15 == x);
+                        CPPUNIT_ASSERT(5 == y);
+                        break;
+                    case 1:
+                        CPPUNIT_ASSERT(40 == x);
+                        CPPUNIT_ASSERT(10 == y);
+                        break;
+                    case 2:
+                        CPPUNIT_ASSERT(10 == x);
+                        CPPUNIT_ASSERT(20 == y);
+                        break;
+                    case 3:
+                        CPPUNIT_ASSERT(5 == x);
+                        CPPUNIT_ASSERT(10 == y);
+                        break;
+                    case 4:
+                        CPPUNIT_ASSERT(15 == x);
+                        CPPUNIT_ASSERT(5 == y);
+                        break;
+                    }
+                }
+            }
+
+            RS_BufferOutputStream ros(bytes->GetLength());
+            lb.ToAgf(&ros);
+
+            Ptr<MgByte> obs = new MgByte(ros.data(), ros.length());
+            Ptr<MgByteSource> bs = new MgByteSource(obs);
+            Ptr<MgByteReader> oagf = bs->GetReader();
+
+            Ptr<MgGeometry> g2 = agfRw.Read(oagf);
+            STRING owkt = wktRw.Write(g2);
+            CPPUNIT_ASSERT(owkt == mpWkt1);
+        }
+
+        //MultiPolygon 2
+        {
+            LineBuffer lb(8);
+            Ptr<MgByteReader> agf = agfRw.Write(gMultiPolygon2);
+            Ptr<MgByteSink> agfSink = new MgByteSink(agf);
+            Ptr<MgByte> bytes = agfSink->ToBuffer();
+            lb.LoadFromAgf(bytes->Bytes(), bytes->GetLength(), nullptr);
+
+            CPPUNIT_ASSERT(2 == lb.geom_count());
+            CPPUNIT_ASSERT(1 == lb.geom_size(0));
+            CPPUNIT_ASSERT(2 == lb.geom_size(1));
+            auto cc = lb.cntr_count();
+            CPPUNIT_ASSERT(3 == cc);
+            auto closed1 = lb.contour_closed(0);
+            auto closed2 = lb.contour_closed(1);
+            auto closed3 = lb.contour_closed(2);
+
+            //Polygon 1
+            {
+                CPPUNIT_ASSERT(4 == lb.cntr_size(0));
+                auto ptOffset = lb.contour_start_point(0);
+                for (int i = 0; i < lb.cntr_size(0); i++)
+                {
+                    auto x = lb.x_coord(ptOffset + i);
+                    auto y = lb.y_coord(ptOffset + i);
+                    switch (i)
+                    {
+                    case 0:
+                        CPPUNIT_ASSERT(40 == x);
+                        CPPUNIT_ASSERT(40 == y);
+                        break;
+                    case 1:
+                        CPPUNIT_ASSERT(20 == x);
+                        CPPUNIT_ASSERT(45 == y);
+                        break;
+                    case 2:
+                        CPPUNIT_ASSERT(45 == x);
+                        CPPUNIT_ASSERT(30 == y);
+                        break;
+                    case 3:
+                        CPPUNIT_ASSERT(40 == x);
+                        CPPUNIT_ASSERT(40 == y);
+                        break;
+                    }
+                }
+            }
+
+            //Polygon 2 (inner ring)
+            {
+                CPPUNIT_ASSERT(6 == lb.cntr_size(1));
+                auto ptOffset = lb.contour_start_point(1);
+                for (int i = 0; i < lb.cntr_size(1); i++)
+                {
+                    auto x = lb.x_coord(ptOffset + i);
+                    auto y = lb.y_coord(ptOffset + i);
+                    switch (i)
+                    {
+                    case 0:
+                        CPPUNIT_ASSERT(20 == x);
+                        CPPUNIT_ASSERT(35 == y);
+                        break;
+                    case 1:
+                        CPPUNIT_ASSERT(10 == x);
+                        CPPUNIT_ASSERT(30 == y);
+                        break;
+                    case 2:
+                        CPPUNIT_ASSERT(10 == x);
+                        CPPUNIT_ASSERT(10 == y);
+                        break;
+                    case 3:
+                        CPPUNIT_ASSERT(30 == x);
+                        CPPUNIT_ASSERT(5 == y);
+                        break;
+                    case 4:
+                        CPPUNIT_ASSERT(45 == x);
+                        CPPUNIT_ASSERT(20 == y);
+                        break;
+                    case 5:
+                        CPPUNIT_ASSERT(20 == x);
+                        CPPUNIT_ASSERT(35 == y);
+                        break;
+                    }
+                }
+            }
+
+            //Polygon 2 (outer ring)
+            {
+                CPPUNIT_ASSERT(4 == lb.cntr_size(2));
+                auto ptOffset = lb.contour_start_point(2);
+                for (int i = 0; i < lb.cntr_size(2); i++)
+                {
+                    auto x = lb.x_coord(ptOffset + i);
+                    auto y = lb.y_coord(ptOffset + i);
+                    switch (i)
+                    {
+                    case 0:
+                        CPPUNIT_ASSERT(30 == x);
+                        CPPUNIT_ASSERT(20 == y);
+                        break;
+                    case 1:
+                        CPPUNIT_ASSERT(20 == x);
+                        CPPUNIT_ASSERT(15 == y);
+                        break;
+                    case 2:
+                        CPPUNIT_ASSERT(20 == x);
+                        CPPUNIT_ASSERT(25 == y);
+                        break;
+                    case 3:
+                        CPPUNIT_ASSERT(30 == x);
+                        CPPUNIT_ASSERT(20 == y);
+                        break;
+                    }
+                }
+            }
+
+            RS_BufferOutputStream ros(bytes->GetLength());
+            lb.ToAgf(&ros);
+            
+            Ptr<MgByte> obs = new MgByte(ros.data(), ros.length());
+            Ptr<MgByteSource> bs = new MgByteSource(obs);
+            Ptr<MgByteReader> oagf = bs->GetReader();
+
+            Ptr<MgGeometry> g2 = agfRw.Read(oagf);
+            STRING owkt = wktRw.Write(g2);
+            CPPUNIT_ASSERT(owkt == mpWkt2);
+        }
+    }
+    catch (MgException* e)
+    {
+        STRING message = e->GetDetails(TEST_LOCALE);
+        SAFE_RELEASE(e);
+        CPPUNIT_FAIL(MG_WCHAR_TO_CHAR(message.c_str()));
+    }
+    catch (...)
+    {
+        throw;
+    }
 }
\ No newline at end of file

Modified: trunk/MgDev/Server/src/UnitTesting/TestMisc.h
===================================================================
--- trunk/MgDev/Server/src/UnitTesting/TestMisc.h	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/UnitTesting/TestMisc.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -35,6 +35,7 @@
     CPPUNIT_TEST(TestCase_TryParseDouble);
     CPPUNIT_TEST(TestCase_BadResourceIdentifier);
     CPPUNIT_TEST(TestCase_ThreadSafeConfiguration);
+    CPPUNIT_TEST(TestCase_LineBuffer_Conversion);
 
     CPPUNIT_TEST(TestEnd); // This must be the very last unit test
     CPPUNIT_TEST_SUITE_END();
@@ -59,6 +60,7 @@
     void TestCase_TryParseDouble();
     void TestCase_BadResourceIdentifier();
     void TestCase_ThreadSafeConfiguration();
+    void TestCase_LineBuffer_Conversion();
 
 private:
     Ptr<MgSiteConnection> m_siteConnection;

Modified: trunk/MgDev/Server/src/UnitTesting/TestRenderingService.cpp
===================================================================
--- trunk/MgDev/Server/src/UnitTesting/TestRenderingService.cpp	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/UnitTesting/TestRenderingService.cpp	2019-10-06 12:18:03 UTC (rev 9630)
@@ -21,8 +21,7 @@
 #include "ServerSiteService.h"
 #include "StylizationDefs.h"
 #include "../Common/Manager/FdoConnectionManager.h"
-//#include "GDRenderer.h"
-//#include "AGGRenderer.h"
+#include "MVTRenderer.h"
 #include "FoundationDefs.h"
 #include "SE_Renderer.h"
 
@@ -1904,6 +1903,72 @@
     }
 }
 
+void TestRenderingService::TestCase_RenderTileMVT()
+{
+    try
+    {
+        Ptr<MgMap> map = CreateTestXYZMap();
+
+        //For ease of visual verfication, render the XYZ image tiles as baseline
+        Ptr<MgByteReader> imgTL = m_svcRendering->RenderTileXYZ(map, L"BaseLayers", 16797, 23893, 16, 96, MgImageFormats::Png);
+        Ptr<MgByteReader> imgTR = m_svcRendering->RenderTileXYZ(map, L"BaseLayers", 16798, 23893, 16, 96, MgImageFormats::Png);
+        Ptr<MgByteReader> imgBL = m_svcRendering->RenderTileXYZ(map, L"BaseLayers", 16797, 23894, 16, 96, MgImageFormats::Png);
+        Ptr<MgByteReader> imgBR = m_svcRendering->RenderTileXYZ(map, L"BaseLayers", 16798, 23894, 16, 96, MgImageFormats::Png);
+
+        imgTL->ToFile(L"../UnitTestFiles/RenderTileMVT_TL_ImageBaseline.png");
+        imgTR->ToFile(L"../UnitTestFiles/RenderTileMVT_TR_ImageBaseline.png");
+        imgBL->ToFile(L"../UnitTestFiles/RenderTileMVT_BL_ImageBaseline.png");
+        imgBR->ToFile(L"../UnitTestFiles/RenderTileMVT_BR_ImageBaseline.png");
+
+        //Now render the MVT tiles at the same place
+        Ptr<MgByteReader> mvtTL = m_svcRendering->RenderTileMVT(map, L"BaseLayers", 16797, 23893, 16, 96);
+        Ptr<MgByteReader> mvtTR = m_svcRendering->RenderTileMVT(map, L"BaseLayers", 16798, 23893, 16, 96);
+        Ptr<MgByteReader> mvtBL = m_svcRendering->RenderTileMVT(map, L"BaseLayers", 16797, 23894, 16, 96);
+        Ptr<MgByteReader> mvtBR = m_svcRendering->RenderTileMVT(map, L"BaseLayers", 16798, 23894, 16, 96);
+
+        CPPUNIT_ASSERT(MgMimeType::Mvt == mvtTL->GetMimeType());
+        CPPUNIT_ASSERT(MgMimeType::Mvt == mvtTR->GetMimeType());
+        CPPUNIT_ASSERT(MgMimeType::Mvt == mvtBL->GetMimeType());
+        CPPUNIT_ASSERT(MgMimeType::Mvt == mvtBR->GetMimeType());
+
+        CPPUNIT_ASSERT(mvtTL->GetLength() > 0);
+        CPPUNIT_ASSERT(mvtTR->GetLength() > 0);
+        CPPUNIT_ASSERT(mvtBL->GetLength() > 0);
+        CPPUNIT_ASSERT(mvtBR->GetLength() > 0);
+
+        mvtTL->ToFile(L"../UnitTestFiles/RenderTileMVT_TL.mvt");
+        mvtTR->ToFile(L"../UnitTestFiles/RenderTileMVT_TR.mvt");
+        mvtBL->ToFile(L"../UnitTestFiles/RenderTileMVT_BL.mvt");
+        mvtBR->ToFile(L"../UnitTestFiles/RenderTileMVT_UR.mvt");
+
+        mvtTL->Rewind();
+        mvtTR->Rewind();
+        mvtBL->Rewind();
+        mvtBR->Rewind();
+
+        Ptr<MgByteSink> sinkTL = new MgByteSink(mvtTL);
+        Ptr<MgByteSink> sinkTR = new MgByteSink(mvtTR);
+        Ptr<MgByteSink> sinkBL = new MgByteSink(mvtBL);
+        Ptr<MgByteSink> sinkBR = new MgByteSink(mvtBR);
+
+        Ptr<MgByte> bytesTL = sinkTL->ToBuffer();
+        Ptr<MgByte> bytesTR = sinkTR->ToBuffer();
+        Ptr<MgByte> bytesBL = sinkBL->ToBuffer();
+        Ptr<MgByte> bytesBR = sinkBR->ToBuffer();
+
+        CPPUNIT_ASSERT(MVTRenderer::IsValidTile(bytesTL->Bytes(), bytesTL->GetLength()));
+        CPPUNIT_ASSERT(MVTRenderer::IsValidTile(bytesTR->Bytes(), bytesTR->GetLength()));
+        CPPUNIT_ASSERT(MVTRenderer::IsValidTile(bytesBL->Bytes(), bytesBL->GetLength()));
+        CPPUNIT_ASSERT(MVTRenderer::IsValidTile(bytesBR->Bytes(), bytesBR->GetLength()));
+    }
+    catch (MgException* e)
+    {
+        STRING message = e->GetDetails(TEST_LOCALE);
+        SAFE_RELEASE(e);
+        CPPUNIT_FAIL(MG_WCHAR_TO_CHAR(message.c_str()));
+    }
+}
+
 STRING TestRenderingService::GetPath(CREFSTRING basePath, CREFSTRING imageFormat, CREFSTRING extension)
 {
     STRING ret;

Modified: trunk/MgDev/Server/src/UnitTesting/TestRenderingService.h
===================================================================
--- trunk/MgDev/Server/src/UnitTesting/TestRenderingService.h	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/UnitTesting/TestRenderingService.h	2019-10-06 12:18:03 UTC (rev 9630)
@@ -74,6 +74,7 @@
     CPPUNIT_TEST(TestCase_RenderTilePNG);
     CPPUNIT_TEST(TestCase_RenderTileXYZ_PNG);
     CPPUNIT_TEST(TestCase_RenderTileUTFGrid);
+    CPPUNIT_TEST(TestCase_RenderTileMVT);
 
     CPPUNIT_TEST(TestCase_StylizationFunctionsPNG8);
 
@@ -178,6 +179,9 @@
     void TestEnd();
 
     void TestCase_QueryFeatures();
+    void TestCase_RenderTileUTFGrid();
+    void TestCase_RenderTileMVT();
+
     void TestCase_RenderMetatile(CREFSTRING imageFormat, CREFSTRING extension);
     void TestCase_RenderXYZMetatile(CREFSTRING imageFormat, CREFSTRING extension, INT32 retinaScale);
     void TestCase_RenderTile(CREFSTRING imageFormat, CREFSTRING extension);
@@ -238,7 +242,6 @@
     void TestCase_LayerWatermarkPNG() { TestCase_LayerWatermark(L"PNG", L"png"); }
     void TestCase_RenderTilePNG() { TestCase_RenderTile(L"PNG", L"png"); }
     void TestCase_RenderTileXYZ_PNG() { TestCase_RenderTileXYZ(L"PNG", L"png"); }
-    void TestCase_RenderTileUTFGrid();
 
     //PNG8 output tests
     void TestCase_RenderDynamicOverlayPNG8() { TestCase_RenderDynamicOverlay(L"PNG8", L"png"); }

Modified: trunk/MgDev/Server/src/UnitTesting/UnitTesting.vcxproj
===================================================================
--- trunk/MgDev/Server/src/UnitTesting/UnitTesting.vcxproj	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/Server/src/UnitTesting/UnitTesting.vcxproj	2019-10-06 12:18:03 UTC (rev 9630)
@@ -94,7 +94,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
-      <AdditionalIncludeDirectories>..\Common;..\Common\Cache;..\Common\Manager;..\Services\Feature;..\Services\Kml;..\Services\Mapping;..\Services\Rendering;..\Services\Resource;..\Services\ServerAdmin;..\Services\Site;..\Services\Tile;..\..\..\Common\Foundation;..\..\..\Common\Geometry;..\..\..\Common\PlatformBase;..\..\..\Common\MapGuideCommon;..\..\..\Common\MdfModel;..\..\..\Common\MdfParser;..\..\..\Common\Stylization;..\..\..\Oem\ACE\ACE_wrappers;..\..\..\Oem\CppUnit-1.9.14\include;..\..\..\Oem\dbxml\xerces-c-src\src;..\..\..\Oem\FDO\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\Common;..\Common\Cache;..\Common\Manager;..\Services\Feature;..\Services\Kml;..\Services\Mapping;..\Services\Rendering;..\Services\Resource;..\Services\ServerAdmin;..\Services\Site;..\Services\Tile;..\..\..\Common\Foundation;..\..\..\Common\Geometry;..\..\..\Common\PlatformBase;..\..\..\Common\MapGuideCommon;..\..\..\Common\MdfModel;..\..\..\Common\MdfParser;..\..\..\Common\Renderers;..\..\..\Common\Stylization;..\..\..\Oem\ACE\ACE_wrappers;..\..\..\Oem\CppUnit-1.9.14\include;..\..\..\Oem\dbxml\xerces-c-src\src;..\..\..\Oem\FDO\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ExceptionHandling>Async</ExceptionHandling>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
@@ -120,7 +120,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
-      <AdditionalIncludeDirectories>..\Common;..\Common\Cache;..\Common\Manager;..\Services\Feature;..\Services\Kml;..\Services\Mapping;..\Services\Rendering;..\Services\Resource;..\Services\ServerAdmin;..\Services\Site;..\Services\Tile;..\..\..\Common\Foundation;..\..\..\Common\Geometry;..\..\..\Common\PlatformBase;..\..\..\Common\MapGuideCommon;..\..\..\Common\MdfModel;..\..\..\Common\MdfParser;..\..\..\Common\Stylization;..\..\..\Oem\ACE\ACE_wrappers;..\..\..\Oem\CppUnit-1.9.14\include;..\..\..\Oem\dbxml\xerces-c-src\src;..\..\..\Oem\FDO\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\Common;..\Common\Cache;..\Common\Manager;..\Services\Feature;..\Services\Kml;..\Services\Mapping;..\Services\Rendering;..\Services\Resource;..\Services\ServerAdmin;..\Services\Site;..\Services\Tile;..\..\..\Common\Foundation;..\..\..\Common\Geometry;..\..\..\Common\PlatformBase;..\..\..\Common\MapGuideCommon;..\..\..\Common\MdfModel;..\..\..\Common\MdfParser;..\..\..\Common\Renderers;..\..\..\Common\Stylization;..\..\..\Oem\ACE\ACE_wrappers;..\..\..\Oem\CppUnit-1.9.14\include;..\..\..\Oem\dbxml\xerces-c-src\src;..\..\..\Oem\FDO\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ExceptionHandling>Async</ExceptionHandling>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
@@ -146,7 +146,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <ClCompile>
       <Optimization>MaxSpeed</Optimization>
-      <AdditionalIncludeDirectories>..\Common;..\Common\Cache;..\Common\Manager;..\Services\Feature;..\Services\Kml;..\Services\Mapping;..\Services\Rendering;..\Services\Resource;..\Services\ServerAdmin;..\Services\Site;..\Services\Tile;..\..\..\Common\Foundation;..\..\..\Common\Geometry;..\..\..\Common\PlatformBase;..\..\..\Common\MapGuideCommon;..\..\..\Common\MdfModel;..\..\..\Common\MdfParser;..\..\..\Common\Stylization;..\..\..\Oem\ACE\ACE_wrappers;..\..\..\Oem\CppUnit-1.9.14\include;..\..\..\Oem\dbxml\xerces-c-src\src;..\..\..\Oem\FDO\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\Common;..\Common\Cache;..\Common\Manager;..\Services\Feature;..\Services\Kml;..\Services\Mapping;..\Services\Rendering;..\Services\Resource;..\Services\ServerAdmin;..\Services\Site;..\Services\Tile;..\..\..\Common\Foundation;..\..\..\Common\Geometry;..\..\..\Common\PlatformBase;..\..\..\Common\MapGuideCommon;..\..\..\Common\MdfModel;..\..\..\Common\MdfParser;..\..\..\Common\Renderers;..\..\..\Common\Stylization;..\..\..\Oem\ACE\ACE_wrappers;..\..\..\Oem\CppUnit-1.9.14\include;..\..\..\Oem\dbxml\xerces-c-src\src;..\..\..\Oem\FDO\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ExceptionHandling>Async</ExceptionHandling>
       <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
@@ -173,7 +173,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <ClCompile>
       <Optimization>MaxSpeed</Optimization>
-      <AdditionalIncludeDirectories>..\Common;..\Common\Cache;..\Common\Manager;..\Services\Feature;..\Services\Kml;..\Services\Mapping;..\Services\Rendering;..\Services\Resource;..\Services\ServerAdmin;..\Services\Site;..\Services\Tile;..\..\..\Common\Foundation;..\..\..\Common\Geometry;..\..\..\Common\PlatformBase;..\..\..\Common\MapGuideCommon;..\..\..\Common\MdfModel;..\..\..\Common\MdfParser;..\..\..\Common\Stylization;..\..\..\Oem\ACE\ACE_wrappers;..\..\..\Oem\CppUnit-1.9.14\include;..\..\..\Oem\dbxml\xerces-c-src\src;..\..\..\Oem\FDO\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\Common;..\Common\Cache;..\Common\Manager;..\Services\Feature;..\Services\Kml;..\Services\Mapping;..\Services\Rendering;..\Services\Resource;..\Services\ServerAdmin;..\Services\Site;..\Services\Tile;..\..\..\Common\Foundation;..\..\..\Common\Geometry;..\..\..\Common\PlatformBase;..\..\..\Common\MapGuideCommon;..\..\..\Common\MdfModel;..\..\..\Common\MdfParser;..\..\..\Common\Renderers;..\..\..\Common\Stylization;..\..\..\Oem\ACE\ACE_wrappers;..\..\..\Oem\CppUnit-1.9.14\include;..\..\..\Oem\dbxml\xerces-c-src\src;..\..\..\Oem\FDO\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <ExceptionHandling>Async</ExceptionHandling>
       <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
@@ -274,6 +274,9 @@
       <Project>{f7334b1b-0efa-47e3-8e66-df158e61b7e4}</Project>
       <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
     </ProjectReference>
+    <ProjectReference Include="..\..\..\Common\Renderers\Renderers.vcxproj">
+      <Project>{38161685-88ed-415e-a545-ccc17be069ae}</Project>
+    </ProjectReference>
     <ProjectReference Include="..\..\..\Common\Security\Security.vcxproj">
       <Project>{7c1c5695-c51c-4017-abef-bc3032cbaf3b}</Project>
       <ReferenceOutputAssembly>false</ReferenceOutputAssembly>

Copied: trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/Library/Samples/Sheboygan/TileSets/SheboyganMVT.TileSetDefinition_CONTENT.xml (from rev 9629, sandbox/jng/mvt_alt/UnitTest/TestData/Samples/Sheboygan/Library/Samples/Sheboygan/TileSets/SheboyganMVT.TileSetDefinition_CONTENT.xml)
===================================================================
--- trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/Library/Samples/Sheboygan/TileSets/SheboyganMVT.TileSetDefinition_CONTENT.xml	                        (rev 0)
+++ trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/Library/Samples/Sheboygan/TileSets/SheboyganMVT.TileSetDefinition_CONTENT.xml	2019-10-06 12:18:03 UTC (rev 9630)
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TileSetDefinition xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:noNamespaceSchemaLocation="TileSetDefinition-3.0.0.xsd">
+  <TileStoreParameters>
+    <TileProvider>XYZ</TileProvider>
+    <Parameter>
+      <Name>TilePath</Name>
+      <Value>%MG_TILE_CACHE_PATH%</Value>
+    </Parameter>
+    <Parameter>
+      <Name>TileFormat</Name>
+      <Value>MVT</Value>
+    </Parameter>
+    <Parameter>
+      <Name>RenderOnly</Name>
+      <Value>false</Value>
+    </Parameter>
+  </TileStoreParameters>
+  <Extents>
+    <MinX>-87.797866013831964</MinX>
+    <MaxX>-87.664527771869246</MaxX>
+    <MinY>43.6868578621819</MinY>
+    <MaxY>43.8037962206133</MaxY>
+  </Extents>
+  <BaseMapLayerGroup>
+    <Name>Base Layer Group</Name>
+    <Visible>true</Visible>
+    <ShowInLegend>true</ShowInLegend>
+    <ExpandInLegend>true</ExpandInLegend>
+    <LegendLabel>Tiled Layers</LegendLabel>
+    <BaseMapLayer>
+      <Name>Roads</Name>
+      <ResourceId>Library://Samples/Sheboygan/Layers/Roads.LayerDefinition</ResourceId>
+      <Selectable>false</Selectable>
+      <ShowInLegend>true</ShowInLegend>
+      <LegendLabel>Roads</LegendLabel>
+      <ExpandInLegend>true</ExpandInLegend>
+    </BaseMapLayer>
+    <BaseMapLayer>
+      <Name>Districts</Name>
+      <ResourceId>Library://Samples/Sheboygan/Layers/Districts.LayerDefinition</ResourceId>
+      <Selectable>false</Selectable>
+      <ShowInLegend>true</ShowInLegend>
+      <LegendLabel>Districts</LegendLabel>
+      <ExpandInLegend>true</ExpandInLegend>
+    </BaseMapLayer>
+    <BaseMapLayer>
+      <Name>Buildings</Name>
+      <ResourceId>Library://Samples/Sheboygan/Layers/Buildings.LayerDefinition</ResourceId>
+      <Selectable>false</Selectable>
+      <ShowInLegend>true</ShowInLegend>
+      <LegendLabel>Buildings</LegendLabel>
+      <ExpandInLegend>true</ExpandInLegend>
+    </BaseMapLayer>
+    <BaseMapLayer>
+      <Name>Parcels</Name>
+      <ResourceId>Library://Samples/Sheboygan/Layers/Parcels.LayerDefinition</ResourceId>
+      <Selectable>true</Selectable>
+      <ShowInLegend>true</ShowInLegend>
+      <LegendLabel>Parcels</LegendLabel>
+      <ExpandInLegend>true</ExpandInLegend>
+    </BaseMapLayer>
+    <BaseMapLayer>
+      <Name>Islands</Name>
+      <ResourceId>Library://Samples/Sheboygan/Layers/Islands.LayerDefinition</ResourceId>
+      <Selectable>false</Selectable>
+      <ShowInLegend>true</ShowInLegend>
+      <LegendLabel>Islands</LegendLabel>
+      <ExpandInLegend>true</ExpandInLegend>
+    </BaseMapLayer>
+    <BaseMapLayer>
+      <Name>Hydrography</Name>
+      <ResourceId>Library://Samples/Sheboygan/Layers/Hydrography.LayerDefinition</ResourceId>
+      <Selectable>false</Selectable>
+      <ShowInLegend>true</ShowInLegend>
+      <LegendLabel>Hydrography</LegendLabel>
+      <ExpandInLegend>true</ExpandInLegend>
+    </BaseMapLayer>
+    <BaseMapLayer>
+      <Name>CityLimits</Name>
+      <ResourceId>Library://Samples/Sheboygan/Layers/CityLimits.LayerDefinition</ResourceId>
+      <Selectable>false</Selectable>
+      <ShowInLegend>true</ShowInLegend>
+      <LegendLabel>CityLimits</LegendLabel>
+      <ExpandInLegend>true</ExpandInLegend>
+    </BaseMapLayer>
+  </BaseMapLayerGroup>
+</TileSetDefinition>
\ No newline at end of file

Copied: trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/Library/Samples/Sheboygan/TileSets/SheboyganMVT.TileSetDefinition_HEADER.xml (from rev 9629, sandbox/jng/mvt_alt/UnitTest/TestData/Samples/Sheboygan/Library/Samples/Sheboygan/TileSets/SheboyganMVT.TileSetDefinition_HEADER.xml)
===================================================================
--- trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/Library/Samples/Sheboygan/TileSets/SheboyganMVT.TileSetDefinition_HEADER.xml	                        (rev 0)
+++ trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/Library/Samples/Sheboygan/TileSets/SheboyganMVT.TileSetDefinition_HEADER.xml	2019-10-06 12:18:03 UTC (rev 9630)
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ResourceDocumentHeader xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:noNamespaceSchemaLocation="ResourceDocumentHeader-1.0.0.xsd">
+  <Security>
+    <Inherited>true</Inherited>
+  </Security>
+</ResourceDocumentHeader>
\ No newline at end of file

Modified: trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/Library/Samples/Sheboygan/TileSets/SheboyganUTFGrid.TileSetDefinition_CONTENT.xml
===================================================================
--- trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/Library/Samples/Sheboygan/TileSets/SheboyganUTFGrid.TileSetDefinition_CONTENT.xml	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/Library/Samples/Sheboygan/TileSets/SheboyganUTFGrid.TileSetDefinition_CONTENT.xml	2019-10-06 12:18:03 UTC (rev 9630)
@@ -12,7 +12,7 @@
     </Parameter>
     <Parameter>
       <Name>RenderOnly</Name>
-      <Value>true</Value>
+      <Value>false</Value>
     </Parameter>
   </TileStoreParameters>
   <Extents>

Modified: trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/MgResourcePackageManifest.xml
===================================================================
--- trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/MgResourcePackageManifest.xml	2019-09-26 08:34:25 UTC (rev 9629)
+++ trunk/MgDev/UnitTest/TestData/Samples/Sheboygan/MgResourcePackageManifest.xml	2019-10-06 12:18:03 UTC (rev 9630)
@@ -2365,7 +2365,7 @@
             <Parameters>
                 <Parameter>
                     <Name>RESOURCEID</Name>
-                    <Value>Library://Samples/Sheboygan/TileSets/SheboyganUTFGrid.TileSetDefinition</Value>
+                    <Value>Library://Samples/Sheboygan/TileSets/SheboyganXYZ.TileSetDefinition</Value>
                 </Parameter>
             </Parameters>
         </Operation>
@@ -2375,17 +2375,17 @@
             <Parameters>
                 <Parameter>
                     <Name>CONTENT</Name>
-                    <Value>Library/Samples/Sheboygan/TileSets/SheboyganUTFGrid.TileSetDefinition_CONTENT.xml</Value>
+                    <Value>Library/Samples/Sheboygan/TileSets/SheboyganXYZ.TileSetDefinition_CONTENT.xml</Value>
                     <ContentType>text/xml</ContentType>
                 </Parameter>
                 <Parameter>
                     <Name>HEADER</Name>
-                    <Value>Library/Samples/Sheboygan/TileSets/SheboyganUTFGrid.TileSetDefinition_HEADER.xml</Value>
+                    <Value>Library/Samples/Sheboygan/TileSets/SheboyganXYZ.TileSetDefinition_HEADER.xml</Value>
                     <ContentType>text/xml</ContentType>
                 </Parameter>
                 <Parameter>
                     <Name>RESOURCEID</Name>
-                    <Value>Library://Samples/Sheboygan/TileSets/SheboyganUTFGrid.TileSetDefinition</Value>
+                    <Value>Library://Samples/Sheboygan/TileSets/SheboyganXYZ.TileSetDefinition</Value>
                 </Parameter>
             </Parameters>
         </Operation>
@@ -2395,7 +2395,7 @@
             <Parameters>
                 <Parameter>
                     <Name>RESOURCEID</Name>
-                    <Value>Library://Samples/Sheboygan/TileSets/SheboyganXYZ.TileSetDefinition</Value>
+                    <Value>Library://Samples/Sheboygan/TileSets/SheboyganXYZRetina.TileSetDefinition</Value>
                 </Parameter>
             </Parameters>
         </Operation>
@@ -2405,17 +2405,17 @@
             <Parameters>
                 <Parameter>
                     <Name>CONTENT</Name>
-                    <Value>Library/Samples/Sheboygan/TileSets/SheboyganXYZ.TileSetDefinition_CONTENT.xml</Value>
+                    <Value>Library/Samples/Sheboygan/TileSets/SheboyganXYZRetina.TileSetDefinition_CONTENT.xml</Value>
                     <ContentType>text/xml</ContentType>
                 </Parameter>
                 <Parameter>
                     <Name>HEADER</Name>
-                    <Value>Library/Samples/Sheboygan/TileSets/SheboyganXYZ.TileSetDefinition_HEADER.xml</Value>
+                    <Value>Library/Samples/Sheboygan/TileSets/SheboyganXYZRetina.TileSetDefinition_HEADER.xml</Value>
                     <ContentType>text/xml</ContentType>
                 </Parameter>
                 <Parameter>
                     <Name>RESOURCEID</Name>
-                    <Value>Library://Samples/Sheboygan/TileSets/SheboyganXYZ.TileSetDefinition</Value>
+                    <Value>Library://Samples/Sheboygan/TileSets/SheboyganXYZRetina.TileSetDefinition</Value>
                 </Parameter>
             </Parameters>
         </Operation>
@@ -2425,7 +2425,7 @@
             <Parameters>
                 <Parameter>
                     <Name>RESOURCEID</Name>
-                    <Value>Library://Samples/Sheboygan/TileSets/SheboyganXYZRetina.TileSetDefinition</Value>
+                    <Value>Library://Samples/Sheboygan/TileSets/SheboyganUTFGrid.TileSetDefinition</Value>
                 </Parameter>
             </Parameters>
         </Operation>
@@ -2435,19 +2435,49 @@
             <Parameters>
                 <Parameter>
                     <Name>CONTENT</Name>
-                    <Value>Library/Samples/Sheboygan/TileSets/SheboyganXYZRetina.TileSetDefinition_CONTENT.xml</Value>
+                    <Value>Library/Samples/Sheboygan/TileSets/SheboyganUTFGrid.TileSetDefinition_CONTENT.xml</Value>
                     <ContentType>text/xml</ContentType>
                 </Parameter>
                 <Parameter>
                     <Name>HEADER</Name>
-                    <Value>Library/Samples/Sheboygan/TileSets/SheboyganXYZRetina.TileSetDefinition_HEADER.xml</Value>
+                    <Value>Library/Samples/Sheboygan/TileSets/SheboyganUTFGrid.TileSetDefinition_HEADER.xml</Value>
                     <ContentType>text/xml</ContentType>
                 </Parameter>
                 <Parameter>
                     <Name>RESOURCEID</Name>
-                    <Value>Library://Samples/Sheboygan/TileSets/SheboyganXYZRetina.TileSetDefinition</Value>
+                    <Value>Library://Samples/Sheboygan/TileSets/SheboyganUTFGrid.TileSetDefinition</Value>
                 </Parameter>
             </Parameters>
         </Operation>
+        <Operation>
+            <Name>DELETERESOURCE</Name>
+            <Version>1.0.0</Version>
+            <Parameters>
+                <Parameter>
+                    <Name>RESOURCEID</Name>
+                    <Value>Library://Samples/Sheboygan/TileSets/SheboyganMVT.TileSetDefinition</Value>
+                </Parameter>
+            </Parameters>
+        </Operation>
+        <Operation>
+            <Name>SETRESOURCE</Name>
+            <Version>1.0.0</Version>
+            <Parameters>
+                <Parameter>
+                    <Name>CONTENT</Name>
+                    <Value>Library/Samples/Sheboygan/TileSets/SheboyganMVT.TileSetDefinition_CONTENT.xml</Value>
+                    <ContentType>text/xml</ContentType>
+                </Parameter>
+                <Parameter>
+                    <Name>HEADER</Name>
+                    <Value>Library/Samples/Sheboygan/TileSets/SheboyganMVT.TileSetDefinition_HEADER.xml</Value>
+                    <ContentType>text/xml</ContentType>
+                </Parameter>
+                <Parameter>
+                    <Name>RESOURCEID</Name>
+                    <Value>Library://Samples/Sheboygan/TileSets/SheboyganMVT.TileSetDefinition</Value>
+                </Parameter>
+            </Parameters>
+        </Operation>
     </Operations>
 </ResourcePackageManifest>
\ No newline at end of file



More information about the mapguide-commits mailing list