"use strict";
/*global require*/
var URI = require("urijs");
var defined = require("terriajs-cesium/Source/Core/defined").default;
var Color = require("terriajs-cesium/Source/Core/Color").default;
const HeightReference = require("terriajs-cesium/Source/Scene/HeightReference")
.default;
const Cartesian2 = require("terriajs-cesium/Source/Core/Cartesian2").default;
var loadJson = require("../Core/loadJson");
var CatalogItem = require("./CatalogItem");
var featureDataToGeoJson = require("../Map/featureDataToGeoJson");
var GeoJsonCatalogItem = require("./GeoJsonCatalogItem");
var inherit = require("../Core/inherit");
var proxyCatalogItemUrl = require("./proxyCatalogItemUrl");
var i18next = require("i18next").default;
/**
* A {@link CatalogItem} representing a layer from an Esri ArcGIS FeatureServer.
*
* @alias ArcGisFeatureServerCatalogItem
* @constructor
* @extends CatalogItem
*
* @param {Terria} terria The Terria instance.
*/
var ArcGisFeatureServerCatalogItem = function(terria) {
CatalogItem.call(this, terria);
this._geoJsonItem = undefined;
/**
* Gets or sets the 'layerDef' string to pass to the server when requesting geometry.
* By default, we use a string that always evaluates to true: "1=1".
* @type {String}
*/
this.layerDef = "1=1";
/**
* If set to true attempts to symbolise the data using the drawingInfo
* object available in the service endpoint. Defaults to false.
* Currently supports getting fill-color, fill-opacity, stroke-color, stroke-width
* from simple and uniqueVals renderers.
* @type {Boolean}
*/
this.useStyleInformationFromService = false;
/**
* Gets or sets a value indicating whether the features in this service should be clamped to the terrain surface.
* @type {Boolean}
*/
this.clampToGround = false;
};
inherit(CatalogItem, ArcGisFeatureServerCatalogItem);
Object.defineProperties(ArcGisFeatureServerCatalogItem.prototype, {
/**
* Gets the type of data item represented by this instance.
* @memberOf ArcGisFeatureServerCatalogItem.prototype
* @type {String}
*/
type: {
get: function() {
return "esri-featureServer";
}
},
/**
* Gets a human-readable name for this type of data source.
* @memberOf ArcGisFeatureServerCatalogItem.prototype
* @type {String}
*/
typeName: {
get: function() {
return i18next.t("models.arcGisFeatureServer.name");
}
},
/**
* Gets the set of functions used to update individual properties in {@link CatalogMember#updateFromJson}.
* When a property name in the returned object literal matches the name of a property on this instance, the value
* will be called as a function and passed a reference to this instance, a reference to the source JSON object
* literal, and the name of the property.
* @memberOf ArcGisFeatureServerCatalogItem.prototype
* @type {Object}
*/
updaters: {
get: function() {
return ArcGisFeatureServerCatalogItem.defaultUpdaters;
}
},
/**
* Gets the set of functions used to serialize individual properties in {@link CatalogMember#serializeToJson}.
* When a property name on the model matches the name of a property in the serializers object lieral,
* the value will be called as a function and passed a reference to the model, a reference to the destination
* JSON object literal, and the name of the property.
* @memberOf ArcGisFeatureServerCatalogItem.prototype
* @type {Object}
*/
serializers: {
get: function() {
return ArcGisFeatureServerCatalogItem.defaultSerializers;
}
},
/**
* Gets the data source associated with this catalog item.
* @memberOf ArcGisFeatureServerCatalogItem.prototype
* @type {DataSource}
*/
dataSource: {
get: function() {
return defined(this._geoJsonItem)
? this._geoJsonItem.dataSource
: undefined;
}
}
});
ArcGisFeatureServerCatalogItem.prototype._getValuesThatInfluenceLoad = function() {
return [this.url, this.layerDef];
};
ArcGisFeatureServerCatalogItem.prototype._load = function() {
var that = this;
this._geoJsonItem = new GeoJsonCatalogItem(this.terria);
this._geoJsonItem.clampToGround = this.clampToGround;
that._geoJsonItem.data = loadGeoJson(that);
if (this.useStyleInformationFromService) {
return loadMetadata(this).then(function(val) {
that._metadata = val;
const renderer = that._metadata.drawingInfo.renderer;
return that._geoJsonItem.load().then(function() {
that.rectangle = that._geoJsonItem.rectangle;
const entities = that.dataSource.entities;
entities.suspendEvents();
// A 'simple' renderer only applies a single style to all features
if (renderer.type === "simple") {
entities.values.forEach(function(entity) {
updateEntityWithEsriStyle(entity, renderer.symbol, that);
});
} else if (renderer.type === "classBreaks") {
// For a 'classBreaks' renderer symbology gets applied via feature properties.
const field = renderer.field;
entities.values.forEach(function(entity) {
const entityVal = entity.properties[field].getValue();
const classBreak = findClassBreak(
entityVal,
renderer.classBreakInfos
);
if (defined(classBreak)) {
updateEntityWithEsriStyle(entity, classBreak.symbol, that);
}
});
} else if (renderer.type === "uniqueValue") {
// For a 'uniqueValue' renderer symbology gets applied via feature properties.
const rendererObj = setupUniqueValRenderer(renderer);
const primaryFieldForSymbology = renderer.field1;
entities.values.forEach(function(entity) {
let symbolName = entity.properties[
primaryFieldForSymbology
].getValue();
// accumulate values if there is more than one field defined
if (renderer.fieldDelimiter && renderer.field2) {
var val2 = entity.properties[renderer.field2].getValue();
if (val2) {
symbolName += renderer.fieldDelimiter + val2;
var val3 = entity.properties[renderer.field3].getValue();
if (val3) {
symbolName += renderer.fieldDelimiter + val3;
}
}
}
let rendererStyle = rendererObj[symbolName];
if (rendererStyle === null) {
rendererStyle = that._metadata.drawingInfo.renderer.defaultSymbol;
}
updateEntityWithEsriStyle(entity, rendererStyle.symbol, that);
});
}
entities.resumeEvents();
});
});
} else {
return that._geoJsonItem.load().then(function() {
that.rectangle = that._geoJsonItem.rectangle;
});
}
};
ArcGisFeatureServerCatalogItem.prototype._enable = function() {
if (defined(this._geoJsonItem)) {
this._geoJsonItem._enable();
}
};
ArcGisFeatureServerCatalogItem.prototype._disable = function() {
if (defined(this._geoJsonItem)) {
this._geoJsonItem._disable();
}
};
ArcGisFeatureServerCatalogItem.prototype._show = function() {
if (defined(this._geoJsonItem)) {
this._geoJsonItem._show();
}
};
ArcGisFeatureServerCatalogItem.prototype._hide = function() {
if (defined(this._geoJsonItem)) {
this._geoJsonItem._hide();
}
};
ArcGisFeatureServerCatalogItem.prototype.showOnSeparateMap = function(
globeOrMap
) {
if (defined(this._geoJsonItem)) {
return this._geoJsonItem.showOnSeparateMap(globeOrMap);
}
};
function setupUniqueValRenderer(renderer) {
const out = {};
for (var i = 0; i < renderer.uniqueValueInfos.length; i++) {
const val = renderer.uniqueValueInfos[i].value;
out[val] = renderer.uniqueValueInfos[i];
}
return out;
}
// classMinValue is an optional key in the classBreakInfo, but classMaxValue is required
// https://developers.arcgis.com/documentation/common-data-types/renderer-objects.htm
function findClassBreak(value, classBreakInfos) {
for (var i = 0; i < classBreakInfos.length; i++) {
const classBreak = classBreakInfos[i];
if (!("classMinValue" in classBreak) && value <= classBreak.classMaxValue) {
return classBreak;
} else if (
"classMinValue" in classBreak &&
value <= classBreak.classMaxValue &&
value >= classBreak.classMinValue
) {
return classBreak;
}
}
}
function updateEntityWithEsriStyle(entity, symbol, catalogItem) {
function convertEsriColorToCesiumColor(esriColor) {
return new Color.fromBytes(
esriColor[0],
esriColor[1],
esriColor[2],
esriColor[3]
);
}
// We're going to replace a general Cesium Point with a billboard
if (defined(entity.point) && defined(symbol.imageData)) {
entity.billboard = {
image: proxyCatalogItemUrl(
catalogItem,
`data:${symbol.contentType};base64,${symbol.imageData}`
),
heightReference: catalogItem.clampToGround
? HeightReference.RELATIVE_TO_GROUND
: null,
width: symbol.width,
height: symbol.height,
rotation: symbol.angle
};
if (defined(symbol.xoffset) || defined(symbol.yoffset)) {
const x = defined(symbol.xoffset) ? symbol.xoffset : 0;
const y = defined(symbol.yoffset) ? symbol.yoffset : 0;
entity.billboard.pixelOffset = new Cartesian2(x, y);
}
entity.point = undefined;
}
// We're going to update the styling of the Cesium Polyline
if (defined(entity.polyline)) {
entity.polyline.material = convertEsriColorToCesiumColor(symbol.color);
entity.polyline.width = symbol.width;
}
// We're going to update the styling of the Cesium Point
if (defined(entity.point)) {
entity.point.color = convertEsriColorToCesiumColor(symbol.color);
entity.point.pixelSize = symbol.size;
if (defined(symbol.outline)) {
entity.point.outlineColor = convertEsriColorToCesiumColor(
symbol.outline.color
);
entity.point.outlineWidth = symbol.outline.width;
} else if (symbol.outline === null) {
entity.point.outlineWidth = 0;
}
}
// We're going to update the styling of the Cesium Polygon
if (defined(entity.polygon)) {
entity.polygon.material = convertEsriColorToCesiumColor(symbol.color);
if (defined(symbol.outline)) {
entity.polygon.outlineColor = convertEsriColorToCesiumColor(
symbol.outline.color
);
entity.polygon.outlineWidth = symbol.outline.width;
}
}
}
function loadGeoJson(item) {
return loadJson(buildGeoJsonUrl(item)).then(function(json) {
return featureDataToGeoJson(json.layers[0]);
});
}
function loadMetadata(item) {
const metaUrl = buildMetdataUrl(item);
return loadJson(metaUrl).then(function(json) {
return json;
});
}
function buildMetdataUrl(catalogItem) {
return proxyCatalogItemUrl(
catalogItem,
new URI(catalogItem.url).addQuery("f", "json").toString()
);
}
function buildGeoJsonUrl(item) {
var url = cleanAndProxyUrl(item, item.url);
var urlComponents = splitLayerIdFromPath(url);
return new URI(urlComponents.urlWithoutLayerId)
.segment("query")
.addQuery("f", "json")
.addQuery(
"layerDefs",
"{" + (urlComponents.layerId || 0) + ':"' + item.layerDef + '"}'
)
.toString();
}
function splitLayerIdFromPath(url) {
var regex = /^(.*)\/(\d+)$/;
var matches = url.match(regex);
if (defined(matches) && matches.length > 2) {
return {
layerId: matches[2],
urlWithoutLayerId: matches[1]
};
}
return {
urlWithoutLayerId: url
};
}
function cleanAndProxyUrl(catalogItem, url) {
return proxyCatalogItemUrl(catalogItem, cleanUrl(url));
}
function cleanUrl(url) {
// Strip off the search portion of the URL
var uri = new URI(url);
uri.search("");
return uri.toString();
}
module.exports = ArcGisFeatureServerCatalogItem;