Let me post the code used to setup the AVL layer and the code used to populate this layer.
Just to clarify my question, the map this occurred on displays green markers or red markers using the ClassBreakMarkerStyle. The green markers are setup in SetupOnTimeClassBreak() and are used for routes that are not late, while the red markers are setup in SetupLateClassBreak() and are used for routes that are late. As you can see in the screenshot there are no red markers showing. It is possible that there are no red markers displayed because there were no routes that were late when this occurred. Alternatively, there might not be any red markers because of some error.
Is it possible for the displayed errors to be caused by the map trying to render a marker that corresponds to a Feature created with bad or incomplete data?
Could errors in an InMemoryMarkerOverlay prevent the background tile, an OpenStreetMapLayer in a LayerOverlay, from rendering properly?
Layer Setup:
/// <summary>
/// Setup the AVL Overlay using an InMemoryMarkerOverlay.
/// </summary>
/// <param name="visible">Boolean indicating whether the Overlay is visible when the map loads.</param>
/// <param name="visibleInOverlaySwitcher">Boolean indicating whether the Overlay is visible in the OverlaySwitcher tool on the map.</param>
/// <returns>InMemoryMarkerOverlay for containing AVL data Markers.</returns>
protected InMemoryMarkerOverlay SetupAVLOverlay(bool visible, bool visibleInOverlaySwitcher)
{
string overlayID = "avlOverlay", overlayName = "AVL Overlay";
//Setup the FeatureSourceColumns.
List<FeatureSourceColumn> columns = new List<FeatureSourceColumn>();
//Add the columns to the list.
columns.Add(new FeatureSourceColumn("Id"));
columns.Add(new FeatureSourceColumn("RouteId"));
columns.Add(new FeatureSourceColumn("TimeStamp"));
columns.Add(new FeatureSourceColumn("Longitude"));
columns.Add(new FeatureSourceColumn("Latitude"));
columns.Add(new FeatureSourceColumn("Speed"));
columns.Add(new FeatureSourceColumn("Heading"));
columns.Add(new FeatureSourceColumn("Route"));
columns.Add(new FeatureSourceColumn("isLate"));
//Create the InMemoryMarkerOverlay from the FeatureSourceColumns list.
InMemoryMarkerOverlay avlOverlay = new InMemoryMarkerOverlay(overlayID, columns);
//Set the Overlay's name, which will be displayed in the Overlay Switcher.
avlOverlay.Name = overlayName;
//Setup styles.
//Create the ClassBreakMarkerStyle.
ClassBreakMarkerStyle cbms = new ClassBreakMarkerStyle("isLate", BreakValueInclusion.IncludeValue);
//Add MarkerClassBreak objects, they need to be added from smallest value to largest value.
cbms.ClassBreaks.Add(SetupOnTimeClassBreak()); //Break Value: 0
cbms.ClassBreaks.Add(SetupLateClassBreak()); //Break Value: 1
//Setup the MarkerStyle on the ZoomLevels.
avlOverlay.ZoomLevelSet.ZoomLevel01.CustomMarkerStyle = cbms;
avlOverlay.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
//Apply projection to layers, the projection must be opened before this is done and should be closed afterwards.
try
{
WGS84toSphMercator.Open();
avlOverlay.FeatureSource.Projection = WGS84toSphMercator;
}
finally
{
WGS84toSphMercator.Close();
}
//Set visibility, and return the constructed overlay.
avlOverlay.IsVisible = visible;
avlOverlay.IsVisibleInOverlaySwitcher = visibleInOverlaySwitcher;
return avlOverlay;
}
/// <summary>
/// Setup MarkerClassBreak for OnTime Routes. Break Value: 0
/// </summary>
/// <returns>A MarkerClassBreak object.</returns>
protected MarkerClassBreak SetupOnTimeClassBreak()
{
//Create the WebImage used for the styles.
WebImage wi = new WebImage();
//Setup the FontStyle and TextOffset.
wi.FontStyle = DefaultFont;
wi.TextOffsetX = 20;
//Set the Text property to use the value in the FeatureSource's Route column.
wi.Text = "[#Route#]";
//Offset the image displayed for the marker, this places the point of the teardrop at the location.
wi.ImageOffsetX = -10.5f;
wi.ImageOffsetY = -25f;
//Set the path for this image.
wi.ImageVirtualPath = @"~/Content/MapIcons/AVLMarkerOnTime.gif";
//Create the MarkerClassBreak with the break value.
MarkerClassBreak mcb = new MarkerClassBreak(0);
//Set the DefaultMarkerStyle's WebImage property to our WebImage.
mcb.DefaultMarkerStyle.WebImage = wi;
//Setup the ContextMenu.
ContextMenu menu = new ContextMenu();
//Setup the Schedule Maintenance ContextMenuItem and add it to the ContextMenu.
ContextMenuItem schedMaint = new ContextMenuItem();
schedMaint.InnerHtml = "Schedule Maintenance";
schedMaint.OnClientClick = "openScheduleMaintenance";
schedMaint.CssClass = "mapMenu";
schedMaint.HoverCssClass = "mapMenuHover";
menu.MenuItems.Add(schedMaint);
//Setup the Extended AVL Info ContextMenuItem and add it to the ContextMenu.
ContextMenuItem extendAVLInfo = new ContextMenuItem();
extendAVLInfo.InnerHtml = "Info";
extendAVLInfo.OnClientClick = "getExtendedAVLInfo";
extendAVLInfo.CssClass = "mapMenuLastItem";
extendAVLInfo.HoverCssClass = "mapMenuHover";
menu.MenuItems.Add(extendAVLInfo);
//Set the ContextMenu width.
menu.Width = 150;
//Set the DefaultMarkStyle.ContextMenu of the MarkerClassBreak to the ContextMenu.
mcb.DefaultMarkerStyle.ContextMenu = menu;
return mcb;
}
/// <summary>
/// Setup MarkerClassBreak for OnTime Routes. Break Value: 1
/// </summary>
/// <returns>A MarkerClassBreak object.</returns>
protected MarkerClassBreak SetupLateClassBreak()
{
//Create the WebImage used for the styles.
WebImage wi = new WebImage();
//Setup the FontStyle and TextOffset.
wi.FontStyle = DefaultFont;
wi.TextOffsetX = 20;
//Set the Text property to use the value in the FeatureSource's Route column.
wi.Text = "[#Route#]";
//Offset the image displayed for the marker, this places the point of the teardrop at the location.
wi.ImageOffsetX = -10.5f;
wi.ImageOffsetY = -25f;
//Set the path for this image.
wi.ImageVirtualPath = @"~/Content/MapIcons/AVLMarkerLate.gif";
//Create the MarkerClassBreak with the break value.
MarkerClassBreak mcb = new MarkerClassBreak(1);
//Set the DefaultMarkerStyle's WebImage property to our WebImage.
mcb.DefaultMarkerStyle.WebImage = wi;
//Setup the ContextMenu.
ContextMenu menu = new ContextMenu();
//Setup the Schedule Maintenance ContextMenuItem and add it to the ContextMenu.
ContextMenuItem schedMaint = new ContextMenuItem();
schedMaint.InnerHtml = "Schedule Maintenance";
schedMaint.OnClientClick = "openScheduleMaintenance";
schedMaint.CssClass = "mapMenu";
schedMaint.HoverCssClass = "mapMenuHover";
menu.MenuItems.Add(schedMaint);
//Setup the Extended AVL Info ContextMenuItem and add it to the ContextMenu.
ContextMenuItem extendAVLInfo = new ContextMenuItem();
extendAVLInfo.InnerHtml = "Info";
extendAVLInfo.OnClientClick = "getExtendedAVLInfo";
extendAVLInfo.CssClass = "mapMenuLastItem";
extendAVLInfo.HoverCssClass = "mapMenuHover";
menu.MenuItems.Add(extendAVLInfo);
//Set the ContextMenu width.
menu.Width = 150;
//Set the DefaultMarkStyle.ContextMenu of the MarkerClassBreak to the ContextMenu.
mcb.DefaultMarkerStyle.ContextMenu = menu;
return mcb;
}
Layer Population (potentitally called every 60 seconds):
/// <summary>
/// Populates the maps AVL data layer.
/// </summary>
/// <param name="map">The ThinkGeo Map object.</param>
/// <param name="data">List of datapoints to populate the overlay.</param>
protected RectangleShape PopulateMapAVLOverlay(Map map, List<vAvlMapData> data)
{
RectangleShape newExtent;
//Get the overlay from the map.
InMemoryMarkerOverlay avlOverlay = (InMemoryMarkerOverlay)map.CustomOverlays[avlOverlayID];
//Clear the InternalFeatures of the overlay. Note we do not need to open and close the FeatureSource when using the InternalFeatures property of the InMemoryFeatureSource.
avlOverlay.FeatureSource.InternalFeatures.Clear();
//Create features from data and add to the overlay.
if (data != null && data.Count > 0)
{
foreach (vAvlMapData datum in data)
{
Dictionary<string, string> dict = new Dictionary<string, string>();
//AVL data.
dict.Add("Id", datum.avlId.ToString());
dict.Add("RouteId", datum.avlrteId.ToString());
dict.Add("TimeStamp", datum.avlTimeStamp.ToString());
dict.Add("Longitude", datum.avlLongitude.ToString());
dict.Add("Latitude", datum.avlLatitude.ToString());
dict.Add("Speed", datum.avlSpeed.ToString());
dict.Add("Heading", datum.avlHeading.ToString());
dict.Add("Route", datum.rteName.ToString());
if (SPs.GetAVLIsLate(datum.avlrteId))
{
dict.Add("isLate", "1");
}
else
{
dict.Add("isLate", "0");
}
//Create a PointShape for the Features position.
PointShape position = MappingUtility.PointShapeFromDBCoords(datum.avlLatitude, datum.avlLongitude);
//Create the Feature and add it the overlay's InternalFeatures.
Feature newFeature = new Feature(new Vertex(position), datum.avlId.ToString(), dict);
avlOverlay.FeatureSource.InternalFeatures.Add(newFeature);
}
}
//Get the bounding box for the Overlay's Features.
if (avlOverlay.FeatureSource.InternalFeatures.Count > 0)
{
try
{
//The FeatureSource must be opened before the GetBoundingBox method is called, and needs to be closed afterwards.
avlOverlay.FeatureSource.Open();
newExtent = avlOverlay.FeatureSource.GetBoundingBox();
}
finally
{
avlOverlay.FeatureSource.Close();
}
}
else
{
//No data in FeatureSource, so use the projected default extent.
newExtent = MappingUtility.GetProjectedDefaultExtent(MappingUtility.WGS84toSphMercator);
}
//Return new map extent
return newExtent;
}