"use strict";
define([
"jquery",
"underscore",
"backbone",
"collections/maps/AssetColors",
], function ($, _, Backbone, AssetColors) {
/**
* @classdesc An AssetColorPalette Model represents a color scale that is
* mapped to some attribute of a Map Asset. For vector assets, like 3D
* tilesets, this palette is used to conditionally color features on a map.
* For any type of asset, it can be used to generate a legend.
* @classcategory Models/Maps
* @class AssetColorPalette
* @name AssetColorPalette
* @extends Backbone.Model
* @since 2.18.0
* @constructor
*/
var AssetColorPalette = Backbone.Model.extend(
/** @lends AssetColorPalette.prototype */ {
/**
* The name of this type of model
* @type {string}
*/
type: "AssetColorPalette",
/**
* Default attributes for AssetColorPalette models
* @name AssetColorPalette#defaults
* @type {Object}
* @property {('categorical'|'continuous'|'classified')}
* [paletteType='categorical'] Set to 'categorical', 'continuous', or
* 'classified'. NOTE: Currently only categorical and continuous palettes
* are supported.
* - Categorical: the color conditions will be interpreted such that one
* color represents a single value (e.g. a discrete palette).
* - Continuous: each color in the colors attribute will represent a point
* in a gradient. The point in the gradient will be associated with the
* number set with the color, and numbers in between points will be set
* to an interpolated color.
* - Classified: the numbers set in the colors attribute will be
* interpreted as maximums. Continuous properties will be forced into
* discrete bins.
* @property {string} property The name (ID) of the property in the asset
* layer's attribute table to color the vector data by (or for imagery
* data that does not have an attribute table, just the name of the
* attribute that these colors map to).
* @property {string} [label = null] A user-friendly name to display
* instead of the actual property name.
* @property {AssetColors} [colors = new AssetColors()] The colors to use
* in the color palette, along with the conditions associated with each
* color (i.e. the properties of the feature that must be true to use the
* given color.) . The last color in the collection will always be treated
* as the default color - any feature that doesn't match the other colors
* will be colored with this color.
* @property {number} [minVal = null] The minimum value of the property to
* use in the color palette when the special value 'min' is used for the
* value of a color.
* @property {number} [maxVal = null] The maximum value of the property to
* use in the color palette when the special value 'max' is used for the
* value of a color.
*/
defaults: function () {
return {
paletteType: "categorical",
property: null,
label: null,
colors: new AssetColors(),
minVal: null,
maxVal: null,
};
},
/**
* The ColorPaletteConfig specifies a color scale that is mapped to some
* attribute of a {@link MapAsset}. For vector assets, like 3D tilesets,
* this palette is used to conditionally color features on a map. For any
* type of asset, including imagery, it can be used to generate a legend.
* The ColorPaletteConfig is passed to a {@link AssetColorPalette} model.
* @typedef {Object} ColorPaletteConfig
* @name MapConfig#ColorPaletteConfig
* @property {('categorical'|'continuous'|'classified')}
* [paletteType='categorical'] NOTE: Currently only categorical and
* continuous palettes are supported.
* - Categorical: the color conditions will be interpreted such that one
* color represents a single value (e.g. a discrete palette).
* - Continuous: each color in the colors attribute will represent a point
* in a gradient. The point in the gradient will be associated with the
* number set with the color, and numbers in between points will be set
* to an interpolated color.
* - Classified: the numbers set in the colors attribute will be
* interpreted as maximums. Continuous properties will be forced into
* discrete bins.
* @property {string} property The name (ID) of the property in the asset
* layer's attribute table to color the vector data by (or for imagery
* data that does not have an attribute table, just the name of the
* attribute that these colors represent).
* @property {string} [label] A user-friendly name to display instead of
* the actual property name.
* @property {MapConfig#ColorConfig[]} colors The colors to use in the
* color palette, along with the conditions associated with each color
* (i.e. the properties of the feature that must be true to use the given
* color). The array of ColorConfig objects are passed to a
* {@link AssetColors} collection, which in turn passes each ColorConfig
* to a {@link AssetColor} model.
*
* @example
* {
* paletteType: 'categorical',
* property: 'landUse',
* label: 'Land Use in 2016',
* colors: [
* { value: "agriculture", color: "#FF5733" },
* { value: "park", color: "#33FF80" }
* ]
* }
*/
/**
* Executed when a new AssetColorPalette model is created.
* @param {MapConfig#ColorPaletteConfig} [paletteConfig] The initial
* values of the attributes, which will be set on the model.
*/
initialize: function (paletteConfig) {
try {
if (paletteConfig && paletteConfig.colors) {
this.set("colors", new AssetColors(paletteConfig.colors));
}
// If a continuous palette has only 1 colour, then treat it as
// categorical
if (
this.get("paletteType") === "continuous" &&
this.get("colors").length === 1
) {
this.set("paletteType", "categorical");
}
} catch (error) {
console.log(
"There was an error initializing a AssetColorPalette model" +
". Error details: " +
error,
);
}
},
/**
* Given properties of a feature, returns the color associated with that
* feature.
* @param {Object} properties The properties of the feature to get the
* color for. (See the 'properties' attribute of
* {@link Feature#defaults}.)
* @returns {AssetColor#Color} The color associated with the given set of
* properties.
*/
getColor: function (properties) {
const colorPalette = this;
// As a backup, use the default color
const defaultColor = colorPalette.getDefaultColor();
let color = defaultColor;
// The name of the property to conditionally color the features by
const prop = colorPalette.get("property");
// The value for the this property in the given properties
const propValue = properties[prop];
// Each palette type has different ways of getting the color
const type = colorPalette.get("paletteType");
// The collection of colors + conditions
let colors = colorPalette.get("colors");
if (!colors || colors.length === 0) {
// Do nothing
} else if (colors.length === 1) {
color = colors.at(0).get("color");
} else if (type === "categorical") {
color = this.getCategoricalColor(propValue);
} else if (type === "classified") {
color = this.getClassifiedColor(propValue);
} else if (type === "continuous") {
color = this.getContinuousColor(propValue);
}
color = color || defaultColor;
return color;
},
/**
* Get the color for a categorical color palette for a given value.
* @param {Number|string} value The value to get the color for.
* @returns {AssetColor#Color} The color associated with the given value.
*/
getCategoricalColor: function (value) {
// For a categorical color palette, the value of the feature property
// just needs to match one of the values in the list of color
// conditions. If it matches, then return the color associated with that
// value.
const colors = this.get("colors");
const colorMatch = colors.findWhere({ value: value });
if (colorMatch) {
return colorMatch.get("color");
}
},
/**
* Get the color for a continuous color palette for a given value.
* @param {Number|string} value The value to get the color for.
*/
getContinuousColor: function (value) {
const collection = this.get("colors");
collection.sort();
const values = collection.getAttr("value");
const colors = collection.getAttr("color");
if (values.indexOf("min") > -1) {
values[values.indexOf("min")] = this.get("minVal");
}
if (values.indexOf("max") > -1) {
values[values.indexOf("max")] = this.get("maxVal");
}
const numColors = colors.length;
let i = 0;
while (i < numColors && value >= values[i]) {
i++;
}
if (i === 0) {
return colors[i];
} else if (i === numColors) {
return colors[i - 1];
} else {
const col1 = colors[i - 1];
const val1 = values[i - 1];
const col2 = colors[i];
const val2 = values[i];
const percent = (value - val1) / (val2 - val1);
return this.interpolate(col1, col2, percent);
}
},
/**
* Get the color for a classified color palette for a given value.
* @param {Number|string} value The value to get the color for.
* @returns {AssetColor#Color} The color for the given value.
*/
getClassifiedColor: function (value) {
// TODO: test TODO: allow "min" and "max" keywords For a classified
// color palette, the value of the feature property needs to be greater
// than or equal to the value of the color condition. Use a sorted
// array. const sortedColors = colors.toArray().sort(function (a, b) {
// return a.get('value') - b.get('value')
// })
// let i = 0; while (i < sortedColors.length && propValue >=
// sortedColors[i].get('value')) { i++;
// }
// color = sortedColors[i].get('color');
},
/**
* Given two colors, returns a color that is a linear interpolation
* between the two colors.
* @param {AssetColor#Color} color1 The first color.
* @param {AssetColor#Color} color2 The second color.
* @param {number} fraction The percentage of the way between the two
* colors, 0-1.
* @returns {AssetColor#Color} The interpolated color.
*/
interpolate: function (color1, color2, fraction) {
const red = color1.red + fraction * (color2.red - color1.red);
const green = color1.green + fraction * (color2.green - color1.green);
const blue = color1.blue + fraction * (color2.blue - color1.blue);
const alpha = color1.alpha + fraction * (color2.alpha - color1.alpha);
return {
red: red,
green: green,
blue: blue,
alpha: alpha,
};
},
/**
* Gets the default color for the color palette, returns it as an object
* of RGB intestines between 0 and 1.
* @returns {AssetColor#Color} The default color for the palette.
*/
getDefaultColor: function () {
return this.get("colors").getDefaultColor().get("color");
},
},
);
return AssetColorPalette;
});