/// <summary>
/// Creates and Serves vector tiles
/// </summary>
/// <param name="boundVariables"></param>
/// <param name="operationInput"></param>
/// <param name="outputFormat"></param>
/// <param name="requestProperties"></param>
/// <param name="responseProperties"></param>
/// <returns></returns>
private byte[] VectorTileHandler(NameValueCollection boundVariables,
JsonObject operationInput,
string outputFormat,
string requestProperties,
out string responseProperties)
{
//responseProperties = null;
responseProperties = null; //"Content-Type:application/json";
ESRI.ArcGIS.SOESupport.AutoTimer timer = new AutoTimer();
const string methodName = "VectorTileHandler";
try
{
long? layerIndex;
long? zoom;
long? row;
long? col;
string jsonFormatParam;
TileFormat jsonFormat = TileFormat.EsriJson; // Defaulting to EsriJson
if (!operationInput.TryGetAsLong("l", out layerIndex))
{
throw new ArgumentNullException("layer");
}
if (!operationInput.TryGetAsLong("z", out zoom))
{
throw new ArgumentNullException("zoom");
}
if (!operationInput.TryGetAsLong("y", out row))
{
throw new ArgumentNullException("row");
}
if (!operationInput.TryGetAsLong("x", out col))
{
throw new ArgumentNullException("col");
}
if (operationInput.TryGetString("jf", out jsonFormatParam))
{
if (!string.IsNullOrEmpty(jsonFormatParam))
{
jsonFormatParam = jsonFormatParam.ToLower().Trim();
Enum.GetNames(typeof(TileFormat)).ToList().ForEach(n =>
{
if (n.ToLower() == jsonFormatParam)
{
jsonFormat = (TileFormat)Enum.Parse(typeof(TileFormat), jsonFormatParam, true);
}
});
}
}
//System.Diagnostics.Debug.WriteLine(string.Format("l:{0}, r:{1}, c:{2}", zoom, row, col));
// Check to see if the tile exists on disk...
// <cache-root>\<layerId>\<zoom>\<row>\<col>.esrijson;
//i.e. to be consistent with Esri tile caching structure
string tilePath = string.Format(@"{0}\{1}\{2}\{3}\{4}.{5}",
_vectorCacheRootDirectory, layerIndex, zoom.Value,
row.Value, col.Value, jsonFormat.ToString().ToLower());
if (File.Exists(tilePath))
{
// Fetch tile contents from disk
_dtsLogger.LogInfo(soe_name, methodName, "Time: " + timer.Elapsed.ToString());
logger.LogMessage(ServerLogger.msgType.infoSimple, methodName, -1, "Time: " + timer.Elapsed.ToString());
return(this.ReadTileFile(tilePath));
}
else
{
// Write new files to disk
IMapServer3 mapServer = serverObjectHelper.ServerObject as IMapServer3;
if (mapServer == null)
{
throw new InvalidOperationException("Unable to access the map server.");
}
// Get the bbox. Returns an envelope in WGS84 (4326).
IEnvelope env102100 = TileUtil.GetEnvelopeFromZoomRowCol((int)zoom.Value, (int)row.Value, (int)col.Value);
//_dtsLogger.LogInfo(soe_name, methodName, this.GeometryToXml(env4326));
// Convert envelope to polygon b/c QueryData does not support spatialfilter geometry using envelope
IPolygon polygon102100 = this.CreatePolygonFromEnvelope(env102100);
// Use QueryData and generalize result geometries based on zoom level
IQueryResultOptions resultOptions = new QueryResultOptionsClass();
// i.e; IRecordSet to support BOTH json and geojson
resultOptions.Format = esriQueryResultFormat.esriQueryResultRecordSetAsObject;
IMapTableDescription tableDescription =
this.GetTableDescription(mapServer, (int)layerIndex, (int)zoom);
// Create spatial filter
ISpatialFilter spatialFilter = new SpatialFilterClass();
spatialFilter.Geometry = polygon102100;
spatialFilter.GeometryField = "Shape";
spatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects;
//TODO: Subfields should be configurable
spatialFilter.SubFields = "*";
// Execute query
IQueryResult result = mapServer.QueryData(mapServer.DefaultMapName,
tableDescription, spatialFilter, resultOptions);
byte[] json = null;
// TODO: Support writing tiles for no data
// Need a way to do this for GeoJson; parse Esri JSON recordset into GeoJson
if (result == null || !this.RecordsetHasFeatures(result.Object as IRecordSet))
{
// Write "no data" tile
if (jsonFormat == TileFormat.EsriJson)
{
resultOptions.Format = esriQueryResultFormat.esriQueryResultJsonAsMime;
result = mapServer.QueryData(mapServer.DefaultMapName, tableDescription, spatialFilter, resultOptions);
json = result.MimeData;
}
else
{
json = StrToByteArray(NO_DATA_GEOJSON);
}
}
else
{
//We have features...
IRecordSet features = result.Object as IRecordSet;
// Get geometry type for query layer
esriGeometryType geometryType = this.GetLayerGeometryType(mapServer.GetServerInfo(mapServer.DefaultMapName).MapLayerInfos, (int)layerIndex);
switch (geometryType)
{
case esriGeometryType.esriGeometryPoint:
// Do nothing... already intersected
json = ESRI.ArcGIS.SOESupport.Conversion.ToJson(features);
break;
case esriGeometryType.esriGeometryPolyline:
// Polylines must be clipped to envelope
IFeatureCursor cursor = null;
this.ClipFeaturesToTile(ref cursor, ref features, env102100);
json = ESRI.ArcGIS.SOESupport.Conversion.ToJson(features);
this.ReleaseComObject(cursor);
break;
case esriGeometryType.esriGeometryMultipoint:
case esriGeometryType.esriGeometryPolygon:
// Get IDs of features whose centroid is contained by tile envelope
List <int> ids = this.GetIdsOfContainedFeatureCentroids(features, polygon102100);
if (ids.Count == 0)
{
// Write no data tile
if (jsonFormat == TileFormat.EsriJson)
{
// Query to get empty featureset and serialize to disk
resultOptions.Format = esriQueryResultFormat.esriQueryResultJsonAsMime;
IQueryFilter queryFilter = new QueryFilterClass();
queryFilter.SubFields = "*";
queryFilter.WhereClause = "1=2";
result = mapServer.QueryData(mapServer.DefaultMapName, tableDescription, queryFilter, resultOptions);
json = result.MimeData;
}
else
{
json = StrToByteArray(NO_DATA_GEOJSON);
}
}
else
{
// Execute new query for IDs
IQueryFilter queryFilter = new QueryFilterClass();
queryFilter.SubFields = "*";
// TODO: Account for sql query syntax based on datasource
// FGDB/Shapefile then "OBJECTID"; quotes required
// PGDB then [OBJECTID]; brackets required
// SDE then OBJECTID; nothing but the fieldname
queryFilter.WhereClause = string.Format("\"OBJECTID\" IN({0})", ids.ToDelimitedString <int>(",")); // FGDB
result = mapServer.QueryData(mapServer.DefaultMapName, tableDescription, queryFilter, resultOptions);
features = result.Object as IRecordSet;
// Do some checking here...
var featureCount = this.GetRecordsetFeatureCount(features);
if (featureCount != ids.Count)
{
System.Diagnostics.Debug.WriteLine(string.Format("Query Problem: ID search results IS NOT EQUAL to contained IDs count - [{0}:{1}]", featureCount, ids.Count));
System.Diagnostics.Debug.WriteLine("Query: " + queryFilter.WhereClause);
}
json = ESRI.ArcGIS.SOESupport.Conversion.ToJson(features);
}
break;
default:
throw new NotSupportedException(string.Format("Geometry type {0} is not supported by {1}", geometryType.ToString(), soe_name));
}
}
//store the json to disk
this.WriteTileFile(json, tilePath, (int)layerIndex, (int)zoom, (int)row);
_dtsLogger.LogInfo(soe_name, methodName, "Time: " + timer.Elapsed.ToString());
logger.LogMessage(ServerLogger.msgType.infoSimple, methodName, -1, "Time: " + timer.Elapsed.ToString());
return(json);
}
}
catch (Exception ex)
{
// Log the error
_dtsLogger.LogError(soe_name, methodName, "n/a", ex);
logger.LogMessage(ServerLogger.msgType.error, methodName, 9999, ex.StackTrace);
return(StrToByteArray("{}"));
}
}