= !MapGuide RFC 140 - Shareable tile sets and XYZ tile rendering support = This page contains a change request (RFC) for the !MapGuide Open Source project. More !MapGuide RFCs can be found on the [wiki:MapGuideRfcs RFCs] page. == Status == ||RFC Template Version||(1.0)|| ||Submission Date||1 July 2014|| ||Last Modified||17 March 2015|| ||Author||Jackie Ng|| ||RFC Status||Implemented|| ||Implementation Status||completed|| ||Proposed Milestone||3.0|| ||Assigned PSC guide(s)||(when determined)|| ||'''Voting History'''||(vote date)|| ||+1||Jackie,Crispin|| ||+0|||| ||-0|||| ||-1|||| ||no vote||Haris,Trevor,Kevin,Zac,Gordon|| '''Author's note (17 March 2015): Upon further analysis, the correct coordinate systems for XYZ tile sets is not LL84, but WGS84.PseudoMercator. This RFC has been amended to reflect this fact''' == Overview == This RFC proposes to enhance the Tiled Map support in MapGuide with shareable tile sets and XYZ tile rendering support == Motivation == The current tiled map support in MapGuide has several limitations: * Tile rendering settings are globally bound to values defined in serverconfig.ini. It is not possible to have tiled maps of various tile sizes and image formats * Tile layers are tied to Map Definitions, preventing re-use of tile caches with other Map Definitions. Such information should ideally be defined in a separate resource. * The root directory for all tile caches is also defined by serverconfig.ini. It is not possible to use alternate paths for storage for certain tiled maps. * The current tile services are not amenable to consumption by external client libraries and applications without intricate knowledge of the MapGuide API and settings of the Map Definition / MgMap. == Proposed Solution == This RFC proposes a multi-pronged approach to solving this problem: * Introducing a new resource type: TileSetDefinition * RenderTile API update * CREATERUNTIMEMAP/DESCRIBERUNTIMEMAP update * Implementing a simple provider model to support different tile sources * New APIs to support Tile Sets * Supporting "XYZ" tile sets Each component is described below === TileSetDefinition === A Tile Set Definition defines a tile cache and its various properties: * The bounds of the tile cache * Parameters that define how this tile cache is accesssed * The layers and groups that consititute the tile set Basically, the Tile Set Definition is effectively the BaseMapDefinition component of the existing Map Definiton schema separated out into its own separate resource. The TileSetDefinition schema is defined as follows: {{{ Defines a tile cache Defines the parameters to access and describe the tile cache A bounding box around the area of the tile cache A group of layers that is used to compose a tiled layer in the HTML viewer TileStoreParameters defines the parameters of this tile cache. The tile image provider Collection of name value pairs for connecting to the tile image provider A type describing name and value pairs Text for the name of parameter Text for value of parameter Box2D encapsulates the the coordinates of a box in 2-D space Minimum x-coordinate Maximum x-coordinate Minimum y-coordinate Maximum y-coordinate BaseMapLayerType encapsulates the properties of a BaseMapLayer. Name of the MapLayer ResourceId of the MapLayer Whether or not the Layer can be selected Whether or not the Layer should be shown in the legend Label to be shown for the Layer in the legend Whether or not the Layer should be expanded in the legend. MapLayerGroupCommonType is a common subclass of MapLayerGroupCommonType and BaseMapLayerGroupCommonType The name of this LayerGroup Whether this group's visiblity should be visible or not when it first comes into range Whether or not the LayerGroup should be shown in the legend Whether or not the LayerGroup should be initially expanded in the legend Label to be shown for the LayerGroup in the legend BaseMapLayerGroupCommonType encapsulates the properties of a BaseMapLayerGroup. It extends MapLayerGroupCommonType by holding the layers in the group. The base map layer groups defines what layers are used to render a tile set in the HTML viewer. The layers that are part of this group. The order of the layers represents the draw order, layers first is the list are drawn over top of layers later in the list. }}} An example tile set definition would look like this: {{{ Default TilePath %MG_TILE_CACHE_PATH% TileWidth 256 TileHeight 256 TileFormat PNG FiniteScaleList 200000,100000,50000,25000,12500,6250,3125,1562.5,781.25,390.625 CoordinateSystem GEOGCS["LL84",DATUM["WGS 84",SPHEROID["WGS 84",6378137,298.25722293287],TOWGS84[0,0,0,0,0,0,0]],PRIMEM["Greenwich",0],UNIT["Degrees",0.01745329252]] -87.79786601383196 -87.66452777186925 43.6868578621819 43.8037962206133 Base Layer Group true true true Tiled Layers Roads Library://Samples/Sheboygan/Layers/Roads.LayerDefinition false true Roads true Districts Library://Samples/Sheboygan/Layers/Districts.LayerDefinition false true Districts true Buildings Library://Samples/Sheboygan/Layers/Buildings.LayerDefinition false true Buildings true Parcels Library://Samples/Sheboygan/Layers/Parcels.LayerDefinition true true Parcels true Islands Library://Samples/Sheboygan/Layers/Islands.LayerDefinition false true Islands true Hydrography Library://Samples/Sheboygan/Layers/Hydrography.LayerDefinition false true Hydrography true CityLimits Library://Samples/Sheboygan/Layers/CityLimits.LayerDefinition false true CityLimits true }}} As you can see, the structure of the document is mostly similar to the BaseMapDefinition section of a Map Definition, with one notable difference. The tile cache settings are defined as a set of key/value pairs for a particular Tile Provider. See the Tile Providers section for more information To render and access tiles in this tile set, clients can use the existing GetTile API of MgTileService, but instead of passing in a Map Definition resource id, they pass in a Tile Set Definition resource id instead. The net result will be the same for both: You will get a rendered tile for the given group/row/col/scale. The difference in this implementation is that, GetTile on the tile set definition will use the defined width/height/format as defined in the Tile Set Definition when rendering (instead of serverconfig.ini). This allows for different tile sets to use different width/height/format/storage settings. To allow for tile set re-usability, Map Definitions can now reference a Tile Set Definition so that its layers and groups can be incorporated to a MgMap as tiled layers/groups. The relevant portion of the new Map Definition schema is shown below {{{ ... ... A reference to the tile set source to use ResourceId of the TileSetDefinition ... ... }}} An example Map Definition that links to a Tile Set Definition looks like this {{{ Base Map linked to Tile Set GEOGCS["WGS84 Lat/Long's, Degrees, -180 ==> +180",DATUM["D_WGS_1984",SPHEROID["World_Geodetic_System_of_1984",6378137,298.257222932867]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -87.764986990962839 -87.695521510899724 43.691398128787782 43.797520000480347 FFF7E1D2 Library://Samples/Sheboygan/TileSets/Sheboygan.TileSetDefinition }}} If for any reason a BaseMapDefinition element and TileSetSource element both exist in the Map Definition, the BaseMapDefinition takes precedence when initializing a MgMap. Authoring tools should try to ensure that only a BaseMapDefinition or TileSetSource is specified but not both. Since both a Tile Set Definition and Map Definition both define coordinate systems and extents. The question may arise as to which one to use when creating a MgMap. The answer is simply if a Map Definition references a Tile Set Definition, then the coordinate system of the Tile Set and the extents of the Map Definition "wins". For example, if the coordinate system of the Map Definition is LL84, and the linked Tile Set Definition is WGS84.PseudoMercator. The coordinate system of the MgMap that is created will be WGS84.PseudoMercator and the extent will be the WGS84.PseudoMercator projected version of the Map Definition's extents. The reason for this rule is simply it is much easier to re-project dynamic layers to the Tile Set's coordinate system rather than re-projecting tiles to the Map Definition's coordinate system. Client authoring tools should do whatever they can to communicate this fact. This support requires some changes to MgLayerGroupType and MgMap in order for applications to identify if it originates from a Map Definition or Tile Set Definition. Firstly, a new value has been defined for MgLayerGroupType to identify layer groups from Tile Sets {{{ class MgLayerGroupType { PUBLISHED_API: ///////////////////////////////////////////////// /// \brief /// Specifies that the layer is a base map layer from a TileSetDefinition resource /// /// \since 3.0 static const INT32 BaseMapFromTileSet = 3; }; }}} From a client application perspective, any MgLayerGroup that is BaseMap or BaseMapFromTileSet should be treated the same for the purposes of: a) Determining if a layer group is tiled b) If a group is suitable for GETTILE requests Secondly, MgMap has a new GetTileSetDefinition() method to identify the Tile Set the group originates from {{{ class MG_PLATFORMBASE_API MgMap : public MgMapBase { PUBLISHED_API: ////////////////////////////////////////////////////////////////// /// \brief /// Returns the resource id of the Tile Set Definition that created /// this map, or the Tile Set Definition linked from the Map Definition /// used to created this map. If it was created from a Map Definition and /// that does not link to a Tile Set Definition, then NULL is returned. /// /// /// \htmlinclude DotNetSyntaxTop.html /// MgResourceIdentifier GetTileSetDefinition(); /// \htmlinclude SyntaxBottom.html /// \htmlinclude JavaSyntaxTop.html /// MgResourceIdentifier GetTileSetDefinition(); /// \htmlinclude SyntaxBottom.html /// \htmlinclude PHPSyntaxTop.html /// MgResourceIdentifier GetTileSetDefinition(); /// \htmlinclude SyntaxBottom.html /// /// \since 3.0 /// /// \return /// Returns the resource id of the Tile Set Definition. NULL if created from a Map Definition that does not link /// to a Tile Set MgResourceIdentifier* GetTileSetDefinition(); }; }}} The existing Create() method of MgMap now also supports Tile Set Definitions meaning it is now possible to create MgMap instances from a Tile Set Definition or a Map Definition. This was done out of necessity as the server implementation of the Tile Service needs to be able to make an MgMap instance to pass to the RenderTile rendering method. For this RFC, we will apply some limitations on what Tile Set Definitions can be passed to MgMap::Create(). See Implications for more information. === RenderTile API update === The RenderTile API is the fundamental primitive that all tile rendering use to produce map tiles. To support tile rendering via TileSetDefinition resources, we'll add a new overload of RenderTile that can accept additional parameters that were previously globally defined in serverconfig.ini {{{ class MG_MAPGUIDE_API MgRenderingService : public MgService { PUBLISHED_API: ///////////////////////////////////////////////////////////////// /// \brief /// Returns the specified base map tile for the given map. /// /// \remarks /// This method only renders the given tile. No tile caching is performed /// by this method. To render and cache the tile, use the /// \link MgTileService::GetTile GetTile \endlink method instead. /// /// \param map /// Input /// map object containing current state of map. /// \param baseMapLayerGroupName /// Input /// Specifies the name of the baseMapLayerGroup for which to render the tile. /// \param tileColumn /// Input /// Specifies the column index of the tile to return. /// \param tileRow /// Input /// Specifies the row index of the tile to return. /// \param tileWidth /// Input /// Specifies the width of the tile to return. /// \param tileHeight /// Input /// Specifies the height of the tile to return. /// \param tileDpi /// Input /// Specifies the dpi the tile to return. /// \param tileImageFormat /// Input /// Specifies the image format of the tile. See \link MgImageFormats \endlink /// /// \return /// A byte reader containing the rendered tile image. /// virtual MgByteReader* RenderTile( MgMap* map, CREFSTRING baseMapLayerGroupName, INT32 tileColumn, INT32 tileRow, INT32 tileWidth, INT32 tileHeight, INT32 tileDpi, CREFSTRING tileImageFormat) = 0; }; }}} === CREATERUNTIMEMAP/DESCRIBERUNTIMEMAP update === This new information also needs to be passed down to whatever responses we send to the client application, so CREATERUNTIMEMAP/DESCRIBERUNTIMEMAP have been updated to include this information: * The Tile Set Definition that the Map Definition links to. If the Map Definition doesn't link to a Tile Set, this element is omitted. * The tile width. If the Map Definition doesn't link to a Tile Set, this element is omitted. * The tile height. If the Map Definition doesn't link to a Tile Set, this element is omitted. The new RuntimeMap XML schema is shown below {{{ Describes information about a Runtime Map, so that client applications can interact with it The MapGuide Site Version The name of the runtime map. This is the value required for any mapagent operation that require a MAPNAME parameter The resource id of the Map Definition from which this runtime map was created from The resource id of the Tile Set Definition that this Map Definition is linked from. If this Map Definition does not link to a tile set, this element is omitted The tile width as defined by the settings in the Tile Set Definition. If this Map Definition does not link to a tile set, this element is omitted The tile height as defined by the settings in the Tile Set Definition. If this Map Definition does not link to a tile set, this element is omitted The map's background color in ARGB hex string format The number of dots per inch of the map display The mime type of all inline icons Describes the coordinate system of the runtime map The WKT string of the coordinate system The CS-Map code of the coordinate system The EPSG code of the coordinate system The meters-per-unit value of the coordinate system Describes a group of Runtime Map Layers The name of the group The type of this group. Can be tiled or dynamic. Uses the value of MgLayerGroupType The group's legend label The group's unique id. Use this value for turning on/off this group in a GETDYNAMICMAPOVERLAYIMAGE request The group's parent group id. Use this value for determining parent-child relationships when building a layer/group hierarchy Indicates whether this group should be displayed in the legend Indicates whether this group should be initially expanded in the legend Indicates whether this group is potentially visible. Note that this may be true even though the group is not visible. This will occur if one of the groups this group is organized within is not visible. Indicates the actual visibility of the group. The visibility depends on the visible property of the group, and the visible property of each group this group is organized within. Describes a runtime instance of a Layer Definition The name of the layer The type of this layer. Can be tiled or dynamic. Uses the value of MgLayerType The layer's legend label The layer's unique id. Use this value for turning on/off this layer in a GETDYNAMICMAPOVERLAYIMAGE request The layer's parent group id. Use this value for determining parent-child relationships when building a layer/group hierarchy Indicates whether this layer should be displayed in the legend Indicates whether this layer should be initially expanded (if layer is themed) in the legend Indicates whether this layer is potentially visible. Note that this may be true even though the layer is not visible. This will occur if the visible flag of one of the groups this layer is organized within is not visible or when the current viewScale property of the map is outside the scale ranges defined for this layer. Indicates the actual visibility of the layer. The visibility depends on the visible property of the group, and the visible property of each group this group is organized within. The Layer Definition from which this runtime layer was created from Describe the Feature Source information for a runtime layer The Feature Source resource id The qualified FDO class name The name of the default Geometry property Describes a scale range of the runtime layer The minimum scale of this scale range The maximum scale of this scale range The feature style for a given geometry type. Defines the style rules for a given geometry type The geometry type that this rule is applicable to (1=point, 2=line, 3=area, 4=composite) Describes a stylization rule in a layer's scale range The legend label for this rule The FDO filter that features must match in order for them to be stylized using this particular rule Defines the icon for this rule. The icon's image content is in base64 format Specifies an envelope (a rectangle) using two corner points. Specifies the lower left corner of the envelope. Specifies the upper right corner of the envelope. }}} The CREATERUNTIMEMAP/DESCRIBERUNTIMEMAP operations can now return either the v2.6 response (for compatibility) or the v3.0 response based on the operation version supplied. Fusion will use the 3.0.0 operation version by default. Other client applications are recommended to use this new version. === Tile Providers === Tile sets are currently managed by MapGuide using globally defined settings in serverconfig.ini. Introducing the concept of Tile Providers allows us to abstract out rendering and tile access specifics to different Tile Providers. An easy way to think about this is FDO for tile access, storage and management with the Tile Set Definition akin to a "Feature Source" for a tile set. For this RFC, we will introduce 2 Tile Providers that can be used by Tile Set Definitions: * Default * XYZ The "Default" provider implements the existing tile rendering and access mechanism. The following parameters can be specified to a "Default" tile provider: * TilePath - Defines where rendered tiles will be cached. This can be any path of your choice or %MG_TILE_CACHE_PATH% to use the MapGuide-designated tile cache path. * TileWidth - Defines the width of tiles that are rendered. Default value is 300. * TileHeight - Defines the height of tiles that are rendered. Default value is 300. * TileFormat - Defines the image format of rendered tiles. Default value is PNG. * RenderOnly - Defines whether tiles rendered for this tile set will be cached or not. Default value is false. * CoordinateSystem - Defines the coordinate system of this tile set. Any layers in this tile set will be re-projected to this coordinate system when rendering if their coordinate system is not the same. * FiniteScaleList - Defines the discrete set of zoom levels for this tile set. The "XYZ" provider implements tile rendering and access using the XYZ tiling scheme. Coordinate System is always ~~LL84~~ WGS84.PseudoMercator and size of rendered tiles are locked at 256x256 pixels. See XYZ support below for more information. "XYZ" provider also uses a simplified directory structure for storing tiles `////.` allowing for easy and intuitive external access if the tile cache was exposed by a web server. The following parameters can be specified to a "XYZ" tile provider: * TilePath - Defines where rendered tiles will be cached. This can be any path of your choice or %MG_TILE_CACHE_PATH% to use the MapGuide-designated tile cache path. * TileFormat - Defines the image format of rendered tiles. Default value is PNG. * RenderOnly - Defines whether tiles rendered for this tile set will be cached or not. Default value is false. Tile access is the same for both Default and XYZ tile providers. For XYZ, the terminology for tile access is as follows: * X = row * Y = column * Z = scale index A new GetTileProviders API is added to MgTileService to return this information: {{{ class MG_MAPGUIDE_API MgTileService : public MgService { PUBLISHED_API: ////////////////////////////////////////////////////////////////// /// \brief /// Returns the list of available tile providers, along with supported connection parameters /// /// \since 3.0 virtual MgByteReader* GetTileProviders() = 0; }; }}} The response is similar to the GetFeatureProvider API in MgFeatureService, providing enough information for client applications like Maestro or Studio to build rich editor user interfaces around. The current response looks like this: {{{ Default Default Tile Provider Default tile access provided by MapGuide. Supports MapGuide-managed tile path or user-defined path TilePath Tile Path %MG_TILE_CACHE_PATH% TileWidth Tile Width 300 TileHeight Tile Height 300 TileFormat Tile Format PNG PNG PNG8 JPG GIF RenderOnly Render Tiles Only (do not cache) false CoordinateSystem Coordinate System FiniteScaleList Finite Display Scale List XYZ XYZ Tile Provider XYZ tile access provided by MapGuide. Grid scheme is compatible with Google Maps and Open Street Map. Rendered tiles are 256x256. Layers must be convertible from WGS84.PseudoMercator coordinates. Under this scheme, Row = X, Column = Y, Scale = Z for accessing tiles. Supports MapGuide-managed tile path or user-defined path TilePath Tile Path %MG_TILE_CACHE_PATH% TileFormat Tile Format PNG PNG PNG8 JPG GIF RenderOnly Render Tiles Only (do not cache) false }}} These tile providers are implemented inside the server implementation of the Tile Service and new initialization logic in MgMap is currently hard-coded against these two providers. There is no scope in this RFC to define a formal plugin API where external Tile Providers can be defined and registered. === XYZ support === XYZ is a tiling scheme used by Google Maps and OpenStreetMap among others that is notable for its ease of consumption by external 3rd party clients with easy to understand rules and methods for determining the values of X,Y and Z to retrieve tiles at any given map viewport. Adding XYZ tile set support to MapGuide increases the accessibility of tiled maps to external 3rd party clients that can access map tiles without needing to know information about the Map Definition itself (eg. meters-per-unit, coordinate system, tile size) in order to know what row/col/scale to issue with GETTILE requests. The existing RenderTile API is not suitable for rendering XYZ tiles. We will introduce a new API to MgRenderingService for this purpose. {{{ class MG_MAPGUIDE_API MgRenderingService : public MgService { PUBLISHED_API: ///////////////////////////////////////////////////////////////// /// \brief /// Returns the specified map tile for the given map. Tile structure is /// based on the XYZ tiling scheme used by Google Maps, OpenStreetMap, and /// others /// /// \param map /// Input /// map object containing current state of map. /// \param baseMapLayerGroupName /// Input /// Specifies the name of the baseMapLayerGroup for which to render the tile. /// \param x /// Input /// Specifies the row index of the tile to return. /// \param y /// Input /// Specifies the column index of the tile to return. /// \param z /// Input /// Specifies the zoom level of the tile to return. /// /// \return /// A byte reader containing the rendered tile image. /// virtual MgByteReader* RenderTileXYZ( MgMap* map, CREFSTRING baseMapLayerGroupName, INT32 x, INT32 y, INT32 z) = 0; ///////////////////////////////////////////////////////////////// /// \brief /// Returns the specified map tile for the given map. Tile structure is /// based on the XYZ tiling scheme used by Google Maps, OpenStreetMap, and /// others /// /// \param map /// Input /// map object containing current state of map. /// \param baseMapLayerGroupName /// Input /// Specifies the name of the baseMapLayerGroup for which to render the tile. /// \param x /// Input /// Specifies the row index of the tile to return. /// \param y /// Input /// Specifies the column index of the tile to return. /// \param z /// Input /// Specifies the zoom level of the tile to return. /// \param dpi /// Input /// Specifies the dpi of the tile to return. /// \param tileImageFormat /// Input /// Specifies the image format of the tile to return. /// /// \return /// A byte reader containing the rendered tile image. /// virtual MgByteReader* RenderTileXYZ( MgMap* map, CREFSTRING baseMapLayerGroupName, INT32 x, INT32 y, INT32 z, INT32 dpi, CREFSTRING tileImageFormat) = 0; }; }}} Based on the given x, y and z values, [http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames we use defined mathematical forumlae] to convert this to a lat/lon bounding box (that is transformed to WGS84.PseudoMercator coordinates when dispatching the actual feature queries for tile rendering). If the coordinate system of the tile set is already ~~lat/lon~~ WGS84.PseudoMercator, the map tile will be rendered for these bounds. Otherwise the bounds will be transformed to the tile set's coordinate system before doing the rendering. Tile Set Definitions using the XYZ provider will be using this API for the actual tile rendering. === APIs to support tile sets === As mentioned above, the raw tile access primitive of GetTile in MgTileService and the v1.2 GETTILE mapagent request requires no modification. The existing method signature satisfies our requirements for tile access via Tile Set Definitions. The API documentation only needs to be updated to specify that the MgResourceIdentifier can now be a Map Definition or a Tile Set Definition. Other APIs in MgTileService will need new overloads to work with tile sets: {{{ class MG_MAPGUIDE_API MgTileService : public MgService { PUBLISHED_API: ////////////////////////////////////////////////////////////////// /// \brief /// Clears the entire tile cache for the given tile set. Tiles for all base /// map layer groups and finite scales will be removed. /// /// \param map /// Input /// Specifies the map whose tile cache will be cleared. /// /// \since 3.0 virtual void ClearCache(MgResourceIdentifier* tileSet) = 0; ////////////////////////////////////////////////////////////////// /// \brief /// Returns the default width of a tile. /// /// \param tileSet /// Input /// Specifies the resource id of the tile set /// /// \return /// Default width of a tile in pixels. /// /// \since 3.0 virtual INT32 GetDefaultTileSizeX(MgResourceIdentifier* tileSet) = 0; ////////////////////////////////////////////////////////////////// /// \brief /// Returns the default height of a tile. /// /// \param tileSet /// Input /// Specifies the resource id of the tile set /// /// \return /// Default height of a tile in pixels. /// /// \since 3.0 virtual INT32 GetDefaultTileSizeY(MgResourceIdentifier* tileSet) = 0; }; }}} == Implications == Existing tiled Map Definitions and tile access methods will behave as before using globally defined settings in serverconfig.ini. An important thing to note as well is that invoking GETTILE on a Map Definition that is linked to a tile set *will not* use the settings defined in that tile set. This is because the server-side does not properly handle the case of a Map Definition that links to a tile set. Any Map Definition resource id is automatically delegated to the existing tile rendering and management code path. A solution does exist to address this, but it appears to be too complex to feasibly make it into this initial implementation. For the sake of simplicity, the API story of GETTILE is: * If used with a Map Definition, serverconfig.ini settings will always be used regardless of whether the Map Definition links to a Tile Set or not. * If used with a Tile Set Definition, settings from that resource will be used. Client applications have already been modified to handle this expectation. An important constraint of the XYZ tiling scheme is that XYZ resolves to real world coordinates. Therefore any map you wish to make available as an XYZ tileset must be in a coordinate system that is either ~~LL84~~ WGS84.PseudoMercator or transformable from ~~LL84~~ WGS84.PseudoMercator. Maps based on arbitrary coordinate systems cannot be served as XYZ tile sets. As mentioned above, the MgMap::Create() method now supports Tile Set Definitions. However for the public API, the method will only support Tile Set Definitions using the "Default" tile provider. The reason for this is that when creating a MgMap, it also needs to be initalized with the coordinate system and finite scale lists, and only Tile Set Definitions using the "Default" tile provider can provide this information. It is not possible to create a MgMap using a Tile Set Definition that uses the XYZ provider. Such attempts will throw a MgUnsupportedTileProviderException. For simplicity, much of the new MgMap initialization logic is hard-coded against the 2 tile providers introduced in this RFC. No formal "plugin" model exists for adding additional tile provider support. Defining a formal plugin API would be the scope of another RFC. Although a Tile Set Definition is sort of a Map Definition and a MgMap can be created from a Tile Set Definition in certain cases, the AJAX and Fusion viewers *will not* be modified to support Web/Flexible Layouts that reference Tile Set Definitions instead of Map Definitions. Authoring tools can and should maintain the constraint that a Web/Flexible Layout must reference a Map Definition. Tile Set Definitions will still be susceptible to shotgun cache invalidation when an upstream resource is saved. No mechanism exists in this RFC to prevent this from happening. But since such information is now separated out into a separate resource, it can pave the way for future API work where tile sets can be locked/unlocked to prevent/allow edits of upstream dependent resources. == Test Plan == Add new MdfModel tests to exercise the new TileSetDefinition schema and ensure all elements are properly read in from sample documents. Add new tile service tests to exercise MgMap creation from Tile Set Definitions and linked Map Definitions and GetTile to TileSetDefinition resources in a multi-threaded fashion. == Funding / Resources == Community