File: ia\custom\WebMap.js
/**
* Creates IA data files from an AGOL web map.
*
* @author J Clare
* @class ia.WebMap
* @constructor
*/
ia.WebMap = function()
{
this._iaData = {};
this._iaMapData = {};
};
/**
* Parses the web map referenced by the url. The callbackFunction returns with
* an iaData object and an iaMapData object.
*
* @method parse
* @param {String} url The url of the web map.
* @param {Function} callbackFunction The call back function.
*/
ia.WebMap.prototype.parse = function(url, callbackFunction)
{
var me = this;
// Initialise IA Data.
this._iaData = {};
this._iaData.version = '1.2';
this._iaData.geographies = new Array();
// Initialise IA MapData.
this._iaMapData = {};
this._iaMapData.layers = new Array();
// Read in the web map data.
ia.File.load(
{
url: url,
dataType: "json",
onSuccess:function(json)
{
// Read in the first operational layer that we find.
var operationalLayers = json.operationalLayers;
if (operationalLayers !== undefined && operationalLayers.length > 0)
{
me.parseOperationalLayer(operationalLayers[0], function()
{
callbackFunction.call(null, me._iaData, me._iaMapData); // return.
});
}
else callbackFunction.call(null, me._iaData, me._iaMapData); // return.
}
});
};
/**
* Parses an operational layer.
*
* @method parseOperationalLayer
* @param {JSON} operationalLayer The AGOL operationalLayer.
* @param {Function} callbackFunction The call back function.
*/
ia.WebMap.prototype.parseOperationalLayer = function(operationalLayer, callbackFunction)
{
var me = this;
if (operationalLayer.url)
{
if (operationalLayer.type === 'CSV') {callbackFunction.call(null);} // return.
else if (operationalLayer.type === 'WMS') {callbackFunction.call(null);} // return.
else if (operationalLayer.type === 'KML') {callbackFunction.call(null);} // return.
else if (operationalLayer.url.indexOf('FeatureServer') !== -1)
{
// Get the feature service description and objectIds.
me.getObjectIds(operationalLayer.url, function(fsLayerDescription, objectIds)
{
// The maxRecordCount is the number of features a feature service will return per request.
// If the feature service has a maxRecordCount set we have to make multiple
// requests to the feature service. If the maxRecordCount is greater than
// the number of features in the feature service then we only need to make one request.
// Set the maxRecordCount.
var maxRecordCount = Infinity;
if (fsLayerDescription.maxRecordCount !== undefined) maxRecordCount = ia.parseInt(fsLayerDescription.maxRecordCount);
maxRecordCount = Math.min(objectIds.length, maxRecordCount);
// Split the objectIds to take into account the maxRecordCount.
var idArray = new Array();
while (objectIds.length > 0) idArray[idArray.length] = objectIds.splice(0, maxRecordCount);
// Makes multiple requests to the feature service until all the features have been returned.
var features = new Array();
var noRequests = idArray.length;
var requestCount = 0;
function onFeaturesReturned(requestedFeatures)
{
// Add the returned features to our master feature array.
features = features.concat(requestedFeatures);
requestCount++;
// When the requestCount is equal to the noRequests we know that
// all the features have been returned.
if (requestCount === noRequests)
{
// Define a new iaGeography.
iaGeography = {};
iaGeography.id = fsLayerDescription.id;
if (operationalLayer.title !== undefined
&& operationalLayer.title !== '') iaGeography.name = operationalLayer.title;
else iaGeography.name = fsLayerDescription.name;
iaGeography.themes = new Array();
iaGeography.features = new Array();
me._iaData.geographies[me._iaData.geographies.length] = iaGeography;
// Geometry.
iaGeography.type = 'polygon'; // default.
if (fsLayerDescription.geometryType === 'esriGeometryPolygon' ) iaGeography.type = 'polygon';
else if (fsLayerDescription.geometryType === 'esriGeometryPoint'
|| fsLayerDescription.geometryType === 'esriGeometryMultipoint') iaGeography.type = 'point';
else if (fsLayerDescription.geometryType === 'esriGeometryPolyline') iaGeography.type = 'line';
// Define a single iaTheme that will hold all the iaIndicators.
var iaTheme = {};
iaTheme.id = 't0';
iaTheme.name = fsLayerDescription.name;
iaTheme.indicators = new Array();
iaGeography.themes[iaGeography.themes.length] = iaTheme;
// Get id and name fields.
var idField = fsLayerDescription.objectIdField;
var nameField = fsLayerDescription.displayField;
if (nameField === undefined || nameField === '') nameField = idField;
// Iterate through the fields to create a new iaIndicators for each field.
var nFields = fsLayerDescription.fields.length;
for (var i = 0; i < nFields; i++)
{
var field = fsLayerDescription.fields[i];
// Exclude id and name fields.
if (field.name !== idField && field.name !== nameField)
{
var iaIndicator = {};
iaIndicator.id = field.name;
if (field.alias !== undefined && field.alias !== '') iaIndicator.name = field.alias
else iaIndicator.name = iaIndicator.id;
iaIndicator.values = new Array();
iaIndicator.type = '';
if (field.type === 'esriFieldTypeSmallInteger'
|| field.type === 'esriFieldTypeInteger'
|| field.type === 'esriFieldTypeSingle'
|| field.type === 'esriFieldTypeDouble'
|| field.type === 'esriFieldTypeInteger') iaIndicator.type = 'numeric';
else if (field.type === 'esriFieldTypeString') iaIndicator.type = 'categoric';
// Only include fields that are numeric or categoric.
if (iaIndicator.type !== '') iaTheme.indicators[iaTheme.indicators.length] = iaIndicator;
}
}
// Iterate through the features.
var featureLength = features.length;
for (var i = 0; i < featureLength; i++)
{
var fsFeature = features[i];
// Define a new iaFeature.
var iaFeature = {};
iaFeature.id = String(fsFeature.attributes[idField]);
if (nameField !== undefined) iaFeature.name = String(fsFeature.attributes[nameField]);
else iaFeature.name = iaFeature.id
// Add the feature to the geography.
iaGeography.features[iaGeography.features.length] = iaFeature;
// Iterate through the indicators to fill in the values for each feature.
var nIndicators = iaTheme.indicators.length;
for (var j = 0; j < nIndicators; j++)
{
iaIndicator = iaTheme.indicators[j]
iaIndicator.values[iaIndicator.values.length] = fsFeature.attributes[iaIndicator.id];
}
}
// Build the iaMap.
var extent = fsLayerDescription.extent;
me._iaMapData.boundingBox = extent.xmin+" "+extent.ymin+" "+extent.xmax+" "+extent.ymax;
// Build the ia base layer
var iaLayer = {};
iaLayer.idField = idField;
iaLayer.nameField = nameField;
iaLayer.url = operationalLayer.url;
iaLayer.srs = extent.spatialReference.wkid;
iaLayer.boundingBox = extent.xmin+" "+extent.ymin+" "+extent.xmax+" "+extent.ymax;
iaLayer.id = fsLayerDescription.id;
iaLayer.name = fsLayerDescription.name;
iaLayer.type = "base-layer";
iaLayer.geometry = iaGeography.type
iaLayer.visible = true;
iaLayer.symbolSize = 15;
iaLayer.fillColor = "#EFEFEF";
iaLayer.fillOpacity = fsLayerDescription.opacity || 0.8;
iaLayer.borderColor = "#cccccc";
iaLayer.borderThickness = 1;
iaLayer.showLabels = false;
iaLayer.iconPath = "";
iaLayer.showDataTips = true;
iaLayer.showInLayerList = true;
me._iaMapData.layers[me._iaMapData.layers.length] = iaLayer;
callbackFunction.call(null); // return.
}
else me.getAttributeData(operationalLayer.url, idArray[requestCount], onFeaturesReturned);
};
me.getAttributeData(operationalLayer.url, idArray[requestCount], onFeaturesReturned);
});
}
else callbackFunction.call(null); // return.
}
else if (operationalLayer.featureCollection !== undefined) {callbackFunction.call(null);} // return.
else callbackFunction.call(null); // return.
};
/**
* The callbackfunction is returned with the feature service description and the list of objectIds.
*
* @method getObjectIds
* @param {JSON} url The url of the feature service.
* @param {Function} callbackFunction The call back function.
*/
ia.WebMap.prototype._useToken = true;
ia.WebMap.prototype.getObjectIds = function(url, callbackFunction)
{
var me = this;
// Load the feature service description.
var queryUrl = url + '?f=json';
if (ia.accessToken !== "" && me._useToken) queryUrl += "&token=" + ia.accessToken;
ia.File.load(
{
url: queryUrl,
dataType: "json",
onSuccess:function(fsLayerDescription)
{
if (fsLayerDescription.error) // Error thrown by feature service.
{
ia.log(fsLayerDescription.error);
if (fsLayerDescription.error.code === 498)
{
// Error: The feature service has thrown an error because even though the web map is
// protected and requires a token the feature service does not, so we drop the token.
me._useToken = false;
return me.getObjectIds(url, callbackFunction)
}
else if (fsLayerDescription.error.code === 499)
{
// Error: The web map is protected and requires the user to log in using oauth2 to get a token.
var authUrl = 'https://www.arcgis.com/sharing/oauth2/authorize?client_id=83wV2txRMBrDpKjq&response_type=token&redirect_uri=' + encodeURI(window.location.href);
window.location.href = authUrl;
}
}
else // No errors.
{
// Get the object ids from the feature service layer.
var queryUrl = url + '/query?where=1+%3D+1&f=json&returnIdsOnly=true';
if (ia.accessToken !== "" && me._useToken) queryUrl += "&token=" + ia.accessToken;
ia.File.load(
{
url: queryUrl,
dataType: "json",
onSuccess:function(fsLayer)
{
callbackFunction.call(null, fsLayerDescription, fsLayer.objectIds); // return.
}
});
}
}
});
};
/**
* Gets the features and their associated attribute data for the given objectIds of a feature service.
*
* @method getAttributeData
* @param {String} url The url of the feature service.
* @return {String[]} objectIds An array of ids.
* @param {Function} callbackFunction The call back function.
* @return {Object[]} A list of features and their attributes.
*/
ia.WebMap.prototype.getAttributeData = function(url, objectIds, callbackFunction)
{
var me = this;
// Get the attribute data.
// Append a query to the url - 'where=1+%3D+1' is just a hack to get back all the features.
var url = url + '/query';
var stringifiedJSON = 'where=1+%3D+1&f=json'
+ '&returnGeometry=false'
+ '&returnIdsOnly=false'
+ '&returnCountOnly=false'
+ '&outFields=*'
+ '&objectIds='+objectIds;
if (ia.accessToken !== "" && me._useToken) stringifiedJSON += "&token=" + ia.accessToken;
ia.File.load(
{
url: url,
type: "POST",
dataType: "json",
data: stringifiedJSON,
onSuccess:function(fsLayer)
{
callbackFunction.call(null, fsLayer.features); // return.
}
});
};