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 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:
<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xs:include schemaLocation="PlatformCommon-1.0.0.xsd"/> <xs:element name="TileSetDefinition"> <xs:annotation> <xs:documentation>Defines a tile cache</xs:documentation> </xs:annotation> <xs:complexType> <xs:sequence> <xs:element name="TileStoreParameters" type="TileStoreParametersType"> <xs:annotation> <xs:documentation>Defines the parameters to access and describe the tile cache</xs:documentation> </xs:annotation> </xs:element> <xs:element name="Extents" type="Box2DType"> <xs:annotation> <xs:documentation>A bounding box around the area of the tile cache</xs:documentation> </xs:annotation> </xs:element> <xs:element name="BaseMapLayerGroup" type="BaseMapLayerGroupCommonType" minOccurs="1" maxOccurs="unbounded"> <xs:annotation> <xs:documentation>A group of layers that is used to compose a tiled layer in the HTML viewer</xs:documentation> </xs:annotation> </xs:element> </xs:sequence> </xs:complexType> </xs:element> <xs:complexType name="TileStoreParametersType"> <xs:annotation> <xs:documentation>TileStoreParameters defines the parameters of this tile cache.</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="TileProvider" type="xs:string"> <xs:annotation> <xs:documentation>The tile image provider</xs:documentation> </xs:annotation> </xs:element> <xs:element name="Parameter" type="NameValuePairType" minOccurs="0" maxOccurs="unbounded"> <xs:annotation> <xs:documentation>Collection of name value pairs for connecting to the tile image provider</xs:documentation> </xs:annotation> </xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="NameValuePairType"> <xs:annotation> <xs:documentation>A type describing name and value pairs</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="Name" type="xs:string"> <xs:annotation> <xs:documentation>Text for the name of parameter</xs:documentation> </xs:annotation> </xs:element> <xs:element name="Value" type="xs:string"> <xs:annotation> <xs:documentation>Text for value of parameter</xs:documentation> </xs:annotation> </xs:element> <xs:element name="ExtendedData1" type="ExtendedDataType" minOccurs="0"/> </xs:sequence> </xs:complexType> <xs:complexType name="Box2DType"> <xs:annotation> <xs:documentation>Box2D encapsulates the the coordinates of a box in 2-D space</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="MinX" type="xs:double"> <xs:annotation> <xs:documentation>Minimum x-coordinate</xs:documentation> </xs:annotation> </xs:element> <xs:element name="MaxX" type="xs:double"> <xs:annotation> <xs:documentation>Maximum x-coordinate</xs:documentation> </xs:annotation> </xs:element> <xs:element name="MinY" type="xs:double"> <xs:annotation> <xs:documentation>Minimum y-coordinate</xs:documentation> </xs:annotation> </xs:element> <xs:element name="MaxY" type="xs:double"> <xs:annotation> <xs:documentation>Maximum y-coordinate</xs:documentation> </xs:annotation> </xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="BaseMapLayerType"> <xs:annotation> <xs:documentation>BaseMapLayerType encapsulates the properties of a BaseMapLayer.</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="Name" type="xs:string"> <xs:annotation> <xs:documentation>Name of the MapLayer</xs:documentation> </xs:annotation> </xs:element> <xs:element name="ResourceId" type="xs:string"> <xs:annotation> <xs:documentation>ResourceId of the MapLayer</xs:documentation> </xs:annotation> </xs:element> <xs:element name="Selectable" type="xs:boolean"> <xs:annotation> <xs:documentation>Whether or not the Layer can be selected</xs:documentation> </xs:annotation> </xs:element> <xs:element name="ShowInLegend" type="xs:boolean"> <xs:annotation> <xs:documentation>Whether or not the Layer should be shown in the legend</xs:documentation> </xs:annotation> </xs:element> <xs:element name="LegendLabel" type="xs:string"> <xs:annotation> <xs:documentation>Label to be shown for the Layer in the legend</xs:documentation> </xs:annotation> </xs:element> <xs:element name="ExpandInLegend" type="xs:boolean"> <xs:annotation> <xs:documentation>Whether or not the Layer should be expanded in the legend.</xs:documentation> </xs:annotation> </xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="MapLayerGroupCommonType"> <xs:annotation> <xs:documentation>MapLayerGroupCommonType is a common subclass of MapLayerGroupCommonType and BaseMapLayerGroupCommonType</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="Name" type="xs:string"> <xs:annotation> <xs:documentation>The name of this LayerGroup</xs:documentation> </xs:annotation> </xs:element> <xs:element name="Visible" type="xs:boolean"> <xs:annotation> <xs:documentation>Whether this group's visiblity should be visible or not when it first comes into range</xs:documentation> </xs:annotation> </xs:element> <xs:element name="ShowInLegend" type="xs:boolean"> <xs:annotation> <xs:documentation>Whether or not the LayerGroup should be shown in the legend</xs:documentation> </xs:annotation> </xs:element> <xs:element name="ExpandInLegend" type="xs:boolean"> <xs:annotation> <xs:documentation>Whether or not the LayerGroup should be initially expanded in the legend</xs:documentation> </xs:annotation> </xs:element> <xs:element name="LegendLabel" type="xs:string"> <xs:annotation> <xs:documentation>Label to be shown for the LayerGroup in the legend</xs:documentation> </xs:annotation> </xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="BaseMapLayerGroupCommonType"> <xs:annotation> <xs:documentation>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.</xs:documentation> </xs:annotation> <xs:complexContent> <xs:extension base="MapLayerGroupCommonType"> <xs:sequence> <xs:element name="BaseMapLayer" type="BaseMapLayerType" minOccurs="0" maxOccurs="unbounded"> <xs:annotation> <xs:documentation>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.</xs:documentation> </xs:annotation> </xs:element> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> </xs:schema>
An example tile set definition would look like this:
<?xml version="1.0" encoding="UTF-8"?> <TileSetDefinition xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="TileSetDefinition-3.0.0.xsd"> <TileStoreParameters> <TileProvider>Default</TileProvider> <Parameter> <Name>TilePath</Name> <Value>%MG_TILE_CACHE_PATH%</Value> </Parameter> <Parameter> <Name>TileWidth</Name> <Value>256</Value> </Parameter> <Parameter> <Name>TileHeight</Name> <Value>256</Value> </Parameter> <Parameter> <Name>TileFormat</Name> <Value>PNG</Value> </Parameter> <Parameter> <Name>FiniteScaleList</Name> <Value>200000,100000,50000,25000,12500,6250,3125,1562.5,781.25,390.625</Value> </Parameter> <Parameter> <Name>CoordinateSystem</Name> <Value>GEOGCS["LL84",DATUM["WGS 84",SPHEROID["WGS 84",6378137,298.25722293287],TOWGS84[0,0,0,0,0,0,0]],PRIMEM["Greenwich",0],UNIT["Degrees",0.01745329252]]</Value> </Parameter> </TileStoreParameters> <Extents> <MinX>-87.79786601383196</MinX> <MaxX>-87.66452777186925</MaxX> <MinY>43.6868578621819</MinY> <MaxY>43.8037962206133</MaxY> </Extents> <BaseMapLayerGroup> <Name>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>
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
<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified" version="3.0.0"> <xs:include schemaLocation="WatermarkDefinition-2.4.0.xsd"/> ... <xs:element name="BaseMapDefinition" minOccurs="0"> ... </xs:element> <xs:element name="TileSetSource" minOccurs="0"> <xs:annotation> <xs:documentation>A reference to the tile set source to use</xs:documentation> </xs:annotation> <xs:complexType> <xs:sequence> <xs:element name="ResourceId" type="xs:string"> <xs:annotation> <xs:documentation>ResourceId of the TileSetDefinition</xs:documentation> </xs:annotation> </xs:element> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="Watermarks" type="WatermarkInstanceCollectionType" minOccurs="0"> ... </xs:element> ... </xs:schema>
An example Map Definition that links to a Tile Set Definition looks like this
<?xml version="1.0" encoding="UTF-8"?> <MapDefinition xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="MapDefinition-3.0.0.xsd" version="3.0.0"> <Name>Base Map linked to Tile Set</Name> <CoordinateSystem>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]]</CoordinateSystem> <Extents> <MinX>-87.764986990962839</MinX> <MaxX>-87.695521510899724</MaxX> <MinY>43.691398128787782</MinY> <MaxY>43.797520000480347</MaxY> </Extents> <BackgroundColor>FFF7E1D2</BackgroundColor> <TileSetSource> <ResourceId>Library://Samples/Sheboygan/TileSets/Sheboygan.TileSetDefinition</ResourceId> </TileSetSource> </MapDefinition>
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. Also if providing specialized editor support for tile sets, it should ensure that any extents that get set for an XYZ tile set definition are WGS84.PseudoMercator-based, doing any coordinate transformation if necessary.
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. /// /// <!-- Syntax in .Net, Java, and PHP --> /// \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
<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xs:element name="RuntimeMap"> <xs:annotation> <xs:documentation>Describes information about a Runtime Map, so that client applications can interact with it</xs:documentation> </xs:annotation> <xs:complexType> <xs:sequence> <xs:element name="SiteVersion" type="xs:string"> <xs:annotation> <xs:documentation>The MapGuide Site Version</xs:documentation> </xs:annotation> </xs:element> <xs:element name="Name" type="xs:string"> <xs:annotation> <xs:documentation>The name of the runtime map. This is the value required for any mapagent operation that require a MAPNAME parameter</xs:documentation> </xs:annotation> </xs:element> <xs:element name="MapDefinition" type="xs:string"> <xs:annotation> <xs:documentation>The resource id of the Map Definition from which this runtime map was created from</xs:documentation> </xs:annotation> </xs:element> <xs:element name="TileSetDefinition" type="xs:string" minOccurs="0"> <xs:annotation> <xs:documentation>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</xs:documentation> </xs:annotation> </xs:element> <xs:element name="TileWidth" type="xs:string" minOccurs="0"> <xs:annotation> <xs:documentation>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</xs:documentation> </xs:annotation> </xs:element> <xs:element name="TileHeight" type="xs:string" minOccurs="0"> <xs:annotation> <xs:documentation>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</xs:documentation> </xs:annotation> </xs:element> <xs:element name="BackgroundColor" type="xs:string"> <xs:annotation> <xs:documentation>The map's background color in ARGB hex string format</xs:documentation> </xs:annotation> </xs:element> <xs:element name="DisplayDpi" type="xs:integer"> <xs:annotation> <xs:documentation>The number of dots per inch of the map display</xs:documentation> </xs:annotation> </xs:element> <xs:element name="IconMimeType" type="xs:string" minOccurs="0"> <xs:annotation> <xs:documentation>The mime type of all inline icons</xs:documentation> </xs:annotation> </xs:element> <xs:element name="CoordinateSystem" type="CoordinateSystemType" /> <xs:element name="Extents" type="Envelope" /> <xs:element name="Group" type="RuntimeMapGroup" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="Layer" type="RuntimeMapLayer" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="FiniteDisplayScale" type="xs:double" minOccurs="0" maxOccurs="unbounded" /> <xs:attribute name="version" type="xs:string" use="required" fixed="3.0.0"/> </xs:sequence> </xs:complexType> </xs:element> <xs:complexType name="CoordinateSystemType"> <xs:annotation> <xs:documentation>Describes the coordinate system of the runtime map</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="Wkt" type="xs:string"> <xs:annotation> <xs:documentation>The WKT string of the coordinate system</xs:documentation> </xs:annotation> </xs:element> <xs:element name="MentorCode" type="xs:string"> <xs:annotation> <xs:documentation>The CS-Map code of the coordinate system</xs:documentation> </xs:annotation> </xs:element> <xs:element name="EpsgCode" type="xs:string"> <xs:annotation> <xs:documentation>The EPSG code of the coordinate system</xs:documentation> </xs:annotation> </xs:element> <xs:element name="MetersPerUnit" type="xs:double"> <xs:annotation> <xs:documentation>The meters-per-unit value of the coordinate system</xs:documentation> </xs:annotation> </xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="RuntimeMapGroup"> <xs:annotation> <xs:documentation>Describes a group of Runtime Map Layers</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="Name" type="xs:string"> <xs:annotation> <xs:documentation>The name of the group</xs:documentation> </xs:annotation> </xs:element> <xs:element name="Type" type="xs:integer"> <xs:annotation> <xs:documentation>The type of this group. Can be tiled or dynamic. Uses the value of MgLayerGroupType</xs:documentation> </xs:annotation> </xs:element> <xs:element name="LegendLabel" type="xs:string"> <xs:annotation> <xs:documentation>The group's legend label</xs:documentation> </xs:annotation> </xs:element> <xs:element name="ObjectId" type="xs:string"> <xs:annotation> <xs:documentation>The group's unique id. Use this value for turning on/off this group in a GETDYNAMICMAPOVERLAYIMAGE request</xs:documentation> </xs:annotation> </xs:element> <xs:element name="ParentId" type="xs:string" minOccurs="0"> <xs:annotation> <xs:documentation>The group's parent group id. Use this value for determining parent-child relationships when building a layer/group hierarchy</xs:documentation> </xs:annotation> </xs:element> <xs:element name="DisplayInLegend" type="xs:boolean"> <xs:annotation> <xs:documentation>Indicates whether this group should be displayed in the legend</xs:documentation> </xs:annotation> </xs:element> <xs:element name="ExpandInLegend" type="xs:boolean"> <xs:annotation> <xs:documentation>Indicates whether this group should be initially expanded in the legend</xs:documentation> </xs:annotation> </xs:element> <xs:element name="Visible" type="xs:boolean"> <xs:annotation> <xs:documentation>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.</xs:documentation> </xs:annotation> </xs:element> <xs:element name="ActuallyVisible" type="xs:boolean"> <xs:annotation> <xs:documentation>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.</xs:documentation> </xs:annotation> </xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="RuntimeMapLayer"> <xs:annotation> <xs:documentation>Describes a runtime instance of a Layer Definition</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="Name" type="xs:string"> <xs:annotation> <xs:documentation>The name of the layer</xs:documentation> </xs:annotation> </xs:element> <xs:element name="Type" type="xs:integer"> <xs:annotation> <xs:documentation>The type of this layer. Can be tiled or dynamic. Uses the value of MgLayerType</xs:documentation> </xs:annotation> </xs:element> <xs:element name="LegendLabel" type="xs:string"> <xs:annotation> <xs:documentation>The layer's legend label</xs:documentation> </xs:annotation> </xs:element> <xs:element name="ObjectId" type="xs:string"> <xs:annotation> <xs:documentation>The layer's unique id. Use this value for turning on/off this layer in a GETDYNAMICMAPOVERLAYIMAGE request</xs:documentation> </xs:annotation> </xs:element> <xs:element name="ParentId" type="xs:string" minOccurs="0"> <xs:annotation> <xs:documentation>The layer's parent group id. Use this value for determining parent-child relationships when building a layer/group hierarchy</xs:documentation> </xs:annotation> </xs:element> <xs:element name="DisplayInLegend" type="xs:boolean"> <xs:annotation> <xs:documentation>Indicates whether this layer should be displayed in the legend</xs:documentation> </xs:annotation> </xs:element> <xs:element name="ExpandInLegend" type="xs:boolean"> <xs:annotation> <xs:documentation>Indicates whether this layer should be initially expanded (if layer is themed) in the legend</xs:documentation> </xs:annotation> </xs:element> <xs:element name="Visible" type="xs:boolean"> <xs:annotation> <xs:documentation>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.</xs:documentation> </xs:annotation> </xs:element> <xs:element name="ActuallyVisible" type="xs:boolean"> <xs:annotation> <xs:documentation>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.</xs:documentation> </xs:annotation> </xs:element> <xs:element name="LayerDefinition" type="xs:string"> <xs:annotation> <xs:documentation>The Layer Definition from which this runtime layer was created from</xs:documentation> </xs:annotation> </xs:element> <xs:element name="FeatureSource" type="FeatureSourceInfo" minOccurs="0" /> <xs:element name="ScaleRange" type="ScaleRangeInfo" minOccurs="0" maxOccurs="unbounded" /> </xs:sequence> </xs:complexType> <xs:complexType name="FeatureSourceInfo"> <xs:annotation> <xs:documentation>Describe the Feature Source information for a runtime layer</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="ResourceId" type="xs:string"> <xs:annotation> <xs:documentation>The Feature Source resource id</xs:documentation> </xs:annotation> </xs:element> <xs:element name="ClassName" type="xs:string"> <xs:annotation> <xs:documentation>The qualified FDO class name</xs:documentation> </xs:annotation> </xs:element> <xs:element name="Geometry" type="xs:string"> <xs:annotation> <xs:documentation>The name of the default Geometry property</xs:documentation> </xs:annotation> </xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="ScaleRangeInfo"> <xs:annotation> <xs:documentation>Describes a scale range of the runtime layer</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="MinScale" type="xs:double"> <xs:annotation> <xs:documentation>The minimum scale of this scale range</xs:documentation> </xs:annotation> </xs:element> <xs:element name="MaxScale" type="xs:double"> <xs:annotation> <xs:documentation>The maximum scale of this scale range</xs:documentation> </xs:annotation> </xs:element> <xs:element name="FeatureStyle" type="FeatureStyleInfo" minOccurs="0" maxOccurs="unbounded"> <xs:annotation> <xs:documentation>The feature style for a given geometry type.</xs:documentation> </xs:annotation> </xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="FeatureStyleInfo"> <xs:annotation> <xs:documentation>Defines the style rules for a given geometry type</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="Type" type="xs:integer"> <xs:annotation> <xs:documentation>The geometry type that this rule is applicable to (1=point, 2=line, 3=area, 4=composite)</xs:documentation> </xs:annotation> </xs:element> <xs:element name="Rule" type="RuleInfo" minOccurs="0" maxOccurs="unbounded" /> </xs:sequence> </xs:complexType> <xs:complexType name="RuleInfo"> <xs:annotation> <xs:documentation>Describes a stylization rule in a layer's scale range</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="LegendLabel" type="xs:string"> <xs:annotation> <xs:documentation>The legend label for this rule</xs:documentation> </xs:annotation> </xs:element> <xs:element name="Filter" type="xs:string"> <xs:annotation> <xs:documentation>The FDO filter that features must match in order for them to be stylized using this particular rule</xs:documentation> </xs:annotation> </xs:element> <xs:element name="Icon" type="xs:string" minOccurs="0"> <xs:annotation> <xs:documentation>Defines the icon for this rule. The icon's image content is in base64 format</xs:documentation> </xs:annotation> </xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="Envelope"> <xs:annotation> <xs:documentation>Specifies an envelope (a rectangle) using two corner points.</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="LowerLeftCoordinate"> <xs:annotation> <xs:documentation>Specifies the lower left corner of the envelope.</xs:documentation> </xs:annotation> <xs:complexType> <xs:sequence> <xs:element name="X" type="xs:string"/> <xs:element name="Y" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="UpperRightCoordinate"> <xs:annotation> <xs:documentation>Specifies the upper right corner of the envelope.</xs:documentation> </xs:annotation> <xs:complexType> <xs:sequence> <xs:element name="X" type="xs:string"/> <xs:element name="Y" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:schema>
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 <basepath>/<group>/<z>/<x>/<y>.<format>
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:
<?xml version="1.0" encoding="UTF-8"?> <TileProviderList> <TileProvider> <Name>Default</Name> <DisplayName>Default Tile Provider</DisplayName> <Description>Default tile access provided by MapGuide. Supports MapGuide-managed tile path or user-defined path</Description> <ConnectionProperties> <ConnectionProperty Enumerable="false" Protected="false" Required="true"> <Name>TilePath</Name> <LocalizedName>Tile Path</LocalizedName> <DefaultValue>%MG_TILE_CACHE_PATH%</DefaultValue> </ConnectionProperty> <ConnectionProperty Enumerable="false" Protected="false" Required="true"> <Name>TileWidth</Name> <LocalizedName>Tile Width</LocalizedName> <DefaultValue>300</DefaultValue> </ConnectionProperty> <ConnectionProperty Enumerable="false" Protected="false" Required="true"> <Name>TileHeight</Name> <LocalizedName>Tile Height</LocalizedName> <DefaultValue>300</DefaultValue> </ConnectionProperty> <ConnectionProperty Enumerable="true" Protected="false" Required="true"> <Name>TileFormat</Name> <LocalizedName>Tile Format</LocalizedName> <DefaultValue>PNG</DefaultValue> <Value>PNG</Value> <Value>PNG8</Value> <Value>JPG</Value> <Value>GIF</Value> </ConnectionProperty> <ConnectionProperty Enumerable="false" Protected="false" Required="false"> <Name>RenderOnly</Name> <LocalizedName>Render Tiles Only (do not cache)</LocalizedName> <DefaultValue>false</DefaultValue> </ConnectionProperty> <ConnectionProperty Enumerable="false" Protected="false" Required="true"> <Name>CoordinateSystem</Name> <LocalizedName>Coordinate System</LocalizedName> <DefaultValue/> </ConnectionProperty> <ConnectionProperty Enumerable="false" Protected="false" Required="true"> <Name>FiniteScaleList</Name> <LocalizedName>Finite Display Scale List</LocalizedName> <DefaultValue/> </ConnectionProperty> </ConnectionProperties> </TileProvider> <TileProvider> <Name>XYZ</Name> <DisplayName>XYZ Tile Provider</DisplayName> <Description>XYZ tile access provided by MapGuide. Grid scheme is compatible with Google Maps and Open Street Map. Rendered tiles are 256x256. Layers must be transformable to WGS84.PseudoMercator coordinates. Under this scheme, Row = X, Column = Y, Scale = Z for accessing tiles. Supports MapGuide-managed tile path or user-defined path</Description> <ConnectionProperties> <ConnectionProperty Enumerable="false" Protected="false" Required="true"> <Name>TilePath</Name> <LocalizedName>Tile Path</LocalizedName> <DefaultValue>%MG_TILE_CACHE_PATH%</DefaultValue> </ConnectionProperty> <ConnectionProperty Enumerable="true" Protected="false" Required="true"> <Name>TileFormat</Name> <LocalizedName>Tile Format</LocalizedName> <DefaultValue>PNG</DefaultValue> <Value>PNG</Value> <Value>PNG8</Value> <Value>JPG</Value> <Value>GIF</Value> </ConnectionProperty> <ConnectionProperty Enumerable="false" Protected="false" Required="false"> <Name>RenderOnly</Name> <LocalizedName>Render Tiles Only (do not cache)</LocalizedName> <DefaultValue>false</DefaultValue> </ConnectionProperty> </ConnectionProperties> </TileProvider> </TileProviderList>
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, 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 to 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