Skip to content

Commit

Permalink
add changeColors() method to SpatialMap
Browse files Browse the repository at this point in the history
  • Loading branch information
bhaller committed Sep 30, 2023
1 parent 86392ef commit 11be0b2
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 58 deletions.
4 changes: 4 additions & 0 deletions QtSLiM/help/SLiMHelpClasses.html
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,10 @@
<p class="p11"><i>5.14.2<span class="Apple-converted-space">  </span></i><span class="s1"><i>SpatialMap</i></span><i> methods</i></p>
<p class="p5">– (object&lt;SpatialMap&gt;$)add(ifo&lt;SpatialMap&gt; x)</p>
<p class="p6">Adds <span class="s1">x</span> to the spatial map.<span class="Apple-converted-space">  </span>One possibility is that <span class="s1">x</span> is a singleton <span class="s1">integer</span> or <span class="s1">float</span> value; in this case, <span class="s1">x</span> is added to each grid value of the target spatial map.<span class="Apple-converted-space">  </span>Another possibility is that <span class="s1">x</span> is an <span class="s1">integer</span> or <span class="s1">float</span> vector/matrix/array of the same dimensions as the target spatial map’s grid; in this case, each value of <span class="s1">x</span> is added to the corresponding grid value of the target spatial map.<span class="Apple-converted-space">  </span>The third possibility is that <span class="s1">x</span> is itself a (singleton) spatial map; in this case, each grid value of <span class="s1">x</span> is added to the corresponding grid value of the target spatial map (and thus the two spatial maps must match in their spatiality, their spatial bounds, and their grid dimensions).<span class="Apple-converted-space">  </span>The target spatial map is returned, to allow easy chaining of operations.</p>
<p class="p5">– (void)changeColors([Nif valueRange = NULL], [Ns colors = NULL])</p>
<p class="p6">Changes the color scheme for the target spatial map.<span class="Apple-converted-space">  </span>The meaning of <span class="s1">valueRange</span> and <span class="s1">colors</span> are identical to their meaning in <span class="s1">defineSpatialMap()</span>, but are also described here.</p>
<p class="p6">The <span class="s1">valueRange</span> and <span class="s1">colors</span> parameters travel together; either both are <span class="s1">NULL</span>, or both are specified.<span class="Apple-converted-space">  </span>They control how map values will be transformed into colors, by SLiMgui and by the <span class="s1">mapColor()</span> method.<span class="Apple-converted-space">  </span>The <span class="s1">valueRange</span> parameter establishes the color-mapped range of spatial map values, as a vector of length two specifying a minimum and maximum; this does not need to match the actual range of values in the map.<span class="Apple-converted-space">  </span>The <span class="s1">colors</span> parameter then establishes the corresponding colors for values within the interval defined by <span class="s1">valueRange</span>: values less than or equal to <span class="s1">valueRange[0]</span> will map to <span class="s1">colors[0]</span>, values greater than or equal to <span class="s1">valueRange[1]</span> will map to the last <span class="s1">colors</span> value, and intermediate values will shade continuously through the specified vector of colors, with interpolation between adjacent colors to produce a continuous spectrum.<span class="Apple-converted-space">  </span>This is much simpler than it sounds in this description; see the recipes in chapter 15 for an illustration of its use.</p>
<p class="p6">If <span class="s1">valueRange</span> and <span class="s1">colors</span> are both <span class="s1">NULL</span>, a default grayscale color scheme will be used in SLiMgui, but an error will result if <span class="s1">mapColor()</span> is called.</p>
<p class="p5">– (void)changeValues(numeric values)</p>
<p class="p6">Changes the grid values used for the target spatial map.<span class="Apple-converted-space">  </span>The <span class="s1">values</span> parameter should be a vector, matrix, or array of numeric values as described in the documentation for <span class="s1">defineSpatialMap()</span>.<span class="Apple-converted-space">  </span>Other characteristics of the spatial map, such as its color mapping (if defined), its spatial bounds, and its spatiality, will remain unchanged.<span class="Apple-converted-space">  </span>The grid resolution of the spatial map is allowed to change with this method.<span class="Apple-converted-space">  </span>This method is useful for changing the values of a spatial map over time, such as to implement changes to the landscape’s characteristics due to seasonality, climate change, processes such as fire or urbanization, and so forth.<span class="Apple-converted-space">  </span>As with the original map values provided to <span class="s1">defineSpatialMap()</span>, it is often useful to read map values from a PNG image file using the Eidos class <span class="s1">Image</span>.</p>
<p class="p5">– (object&lt;SpatialMap&gt;$)divide(ifo&lt;SpatialMap&gt; x)</p>
Expand Down
44 changes: 44 additions & 0 deletions SLiMgui/SLiMHelpClasses.rtf
Original file line number Diff line number Diff line change
Expand Up @@ -5744,6 +5744,50 @@ If the model is being profiled, or is executing forward to a tick number entered
\f4\fs20 is added to the corresponding grid value of the target spatial map (and thus the two spatial maps must match in their spatiality, their spatial bounds, and their grid dimensions). The target spatial map is returned, to allow easy chaining of operations.\
\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0

\f3\fs18 \cf2 \'96\'a0(void)changeColors([Nif\'a0valueRange\'a0=\'a0NULL], [Ns\'a0colors\'a0=\'a0NULL])\
\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0

\f4\fs20 \cf2 Changes the color scheme for the target spatial map. The meaning of
\f3\fs18 valueRange
\f4\fs20 and
\f3\fs18 colors
\f4\fs20 are identical to their meaning in
\f3\fs18 defineSpatialMap()
\f4\fs20 , but are also described here.\
The
\f3\fs18 valueRange
\f4\fs20 and
\f3\fs18 colors
\f4\fs20 parameters travel together; either both are
\f3\fs18 NULL
\f4\fs20 , or both are specified. They control how map values will be transformed into colors, by SLiMgui and by the
\f3\fs18 mapColor()
\f4\fs20 method. The
\f3\fs18 valueRange
\f4\fs20 parameter establishes the color-mapped range of spatial map values, as a vector of length two specifying a minimum and maximum; this does not need to match the actual range of values in the map. The
\f3\fs18 colors
\f4\fs20 parameter then establishes the corresponding colors for values within the interval defined by
\f3\fs18 valueRange
\f4\fs20 : values less than or equal to
\f3\fs18 valueRange[0]
\f4\fs20 will map to
\f3\fs18 colors[0]
\f4\fs20 , values greater than or equal to
\f3\fs18 valueRange[1]
\f4\fs20 will map to the last
\f3\fs18 colors
\f4\fs20 value, and intermediate values will shade continuously through the specified vector of colors, with interpolation between adjacent colors to produce a continuous spectrum. This is much simpler than it sounds in this description; see the recipes in chapter 15 for an illustration of its use.\
If
\f3\fs18 valueRange
\f4\fs20 and
\f3\fs18 colors
\f4\fs20 are both
\f3\fs18 NULL
\f4\fs20 , a default grayscale color scheme will be used in SLiMgui, but an error will result if
\f3\fs18 mapColor()
\f4\fs20 is called.\
\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0

\f3\fs18 \cf2 \'96\'a0(void)changeValues(numeric\'a0values)\
\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0

Expand Down
1 change: 1 addition & 0 deletions VERSIONS
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ development head (in the master branch):
tweak WF reproduction, addCrossed(), addSelfed(), addCloned(), and addRecombinant() to give the new offspring the same spatial position as the first parent, allowing efficient positioning with pointDeviated() in the typical usage case
policy change: type 'f' kernels now require a finite maxDistance, since the alternative doesn't really make sense (note that type 'l' already required a finite maxDistance)
new option in the Individuals view for spatial models, to display the underlying grid points of the spatial map
add changeColors() method to SpatialMap


version 4.0.1 (Eidos version 3.0.1):
Expand Down
1 change: 1 addition & 0 deletions core/slim_globals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1370,6 +1370,7 @@ const std::string &gStr_subtract = EidosRegisteredString("subtract", gID_subtrac
const std::string &gStr_divide = EidosRegisteredString("divide", gID_divide);
const std::string &gStr_power = EidosRegisteredString("power", gID_power);
const std::string &gStr_exp = EidosRegisteredString("exp", gID_exp);
const std::string &gStr_changeColors = EidosRegisteredString("changeColors", gID_changeColors);
const std::string &gStr_changeValues = EidosRegisteredString("changeValues", gID_changeValues);
const std::string &gStr_gridValues = EidosRegisteredString("gridValues", gID_gridValues);
const std::string &gStr_mapColor = EidosRegisteredString("mapColor", gID_mapColor);
Expand Down
2 changes: 2 additions & 0 deletions core/slim_globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,7 @@ extern const std::string &gStr_divide;
extern const std::string &gStr_subtract;
extern const std::string &gStr_power;
extern const std::string &gStr_exp;
extern const std::string &gStr_changeColors;
extern const std::string &gStr_changeValues;
extern const std::string &gStr_gridValues;
extern const std::string &gStr_mapColor;
Expand Down Expand Up @@ -1351,6 +1352,7 @@ enum _SLiMGlobalStringID : int {
gID_divide,
gID_power,
gID_exp,
gID_changeColors,
gID_changeValues,
gID_gridValues,
gID_mapColor,
Expand Down
127 changes: 73 additions & 54 deletions core/spatial_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,60 +126,7 @@ SpatialMap::SpatialMap(std::string p_name, std::string p_spatiality_string, Subp
EIDOS_TERMINATION << "ERROR (SpatialMap::SpatialMap): defineSpatialMap() spatiality \"" << spatiality_string_ << "\" must be \"x\", \"y\", \"z\", \"xy\", \"xz\", \"yz\", or \"xyz\"." << EidosTerminate();

TakeValuesFromEidosValue(p_values, "SpatialMap::SpatialMap", "defineSpatialMap()");

// Make our color map
const double *values_float_vec_ptr = (p_values->Type() == EidosValueType::kValueFloat) ? p_values->FloatVector()->data() : nullptr;
const int64_t *values_integer_vec_ptr = (p_values->Type() == EidosValueType::kValueInt) ? p_values->IntVector()->data() : nullptr;

assert(values_float_vec_ptr || values_integer_vec_ptr);

bool range_is_null = (p_value_range->Type() == EidosValueType::kValueNULL);
bool colors_is_null = (p_colors->Type() == EidosValueType::kValueNULL);

n_colors_ = 0;

if (!range_is_null || !colors_is_null)
{
if (range_is_null || colors_is_null)
EIDOS_TERMINATION << "ERROR (SpatialMap::SpatialMap): defineSpatialMap() valueRange and colors must either both be supplied, or neither supplied." << EidosTerminate();

if (p_value_range->Count() != 2)
EIDOS_TERMINATION << "ERROR (SpatialMap::SpatialMap): defineSpatialMap() valueRange must be exactly length 2 (giving the min and max value permitted)." << EidosTerminate();

// valueRange and colors were provided, so use them for coloring
colors_min_ = p_value_range->FloatAtIndex(0, nullptr);
colors_max_ = p_value_range->FloatAtIndex(1, nullptr);

if (!std::isfinite(colors_min_) || !std::isfinite(colors_max_) || (colors_min_ > colors_max_))
EIDOS_TERMINATION << "ERROR (SpatialMap::SpatialMap): defineSpatialMap() valueRange must be finite, and min <= max is required." << EidosTerminate();

n_colors_ = p_colors->Count();

if (n_colors_ < 2)
EIDOS_TERMINATION << "ERROR (SpatialMap::SpatialMap): defineSpatialMap() colors must be of length >= 2." << EidosTerminate();
}

// Allocate buffers to hold our color component vectors, if we were supplied with color info
if (n_colors_ > 0)
{
red_components_ = (float *)malloc(n_colors_ * sizeof(float));
green_components_ = (float *)malloc(n_colors_ * sizeof(float));
blue_components_ = (float *)malloc(n_colors_ * sizeof(float));

if (!red_components_ || !green_components_ || !blue_components_)
EIDOS_TERMINATION << "ERROR (SpatialMap::SpatialMap): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr);

const std::string *colors_vec_ptr = p_colors->StringVector()->data();

for (int colors_index = 0; colors_index < n_colors_; ++colors_index)
Eidos_GetColorComponents(colors_vec_ptr[colors_index], red_components_ + colors_index, green_components_ + colors_index, blue_components_ + colors_index);
}
else
{
red_components_ = nullptr;
green_components_ = nullptr;
blue_components_ = nullptr;
}
TakeColorsFromEidosValues(p_value_range, p_colors, "SpatialMap::SpatialMap", "defineSpatialMap()");
}

SpatialMap::SpatialMap(std::string p_name, SpatialMap &p_original) :
Expand Down Expand Up @@ -277,6 +224,63 @@ void SpatialMap::_ValuesChanged(void)
EIDOS_TERMINATION << "ERROR (SpatialMap::_ValuesChanged): non-finite values (infinities, NANs) are not allowed in SpatialMap." << EidosTerminate();
}

void SpatialMap::TakeColorsFromEidosValues(EidosValue *p_value_range, EidosValue *p_colors, std::string p_code_name, std::string p_eidos_name)
{
// Make our color map
bool range_is_null = (p_value_range->Type() == EidosValueType::kValueNULL);
bool colors_is_null = (p_colors->Type() == EidosValueType::kValueNULL);

n_colors_ = 0;

if (!range_is_null || !colors_is_null)
{
if (range_is_null || colors_is_null)
EIDOS_TERMINATION << "ERROR (" << p_code_name << "): " << p_eidos_name << " valueRange and colors must either both be supplied, or neither supplied." << EidosTerminate();

if (p_value_range->Count() != 2)
EIDOS_TERMINATION << "ERROR (" << p_code_name << "): " << p_eidos_name << " valueRange must be exactly length 2 (giving the min and max value permitted)." << EidosTerminate();

// valueRange and colors were provided, so use them for coloring
colors_min_ = p_value_range->FloatAtIndex(0, nullptr);
colors_max_ = p_value_range->FloatAtIndex(1, nullptr);

if (!std::isfinite(colors_min_) || !std::isfinite(colors_max_) || (colors_min_ > colors_max_))
EIDOS_TERMINATION << "ERROR (" << p_code_name << "): " << p_eidos_name << " valueRange must be finite, and min <= max is required." << EidosTerminate();

n_colors_ = p_colors->Count();

if (n_colors_ < 2)
EIDOS_TERMINATION << "ERROR (" << p_code_name << "): " << p_eidos_name << " colors must be of length >= 2." << EidosTerminate();
}

// Allocate buffers to hold our color component vectors, if we were supplied with color info
if (n_colors_ > 0)
{
red_components_ = (float *)malloc(n_colors_ * sizeof(float));
green_components_ = (float *)malloc(n_colors_ * sizeof(float));
blue_components_ = (float *)malloc(n_colors_ * sizeof(float));

if (!red_components_ || !green_components_ || !blue_components_)
EIDOS_TERMINATION << "ERROR (" << p_code_name << "): allocation failed; you may need to raise the memory limit for SLiM." << EidosTerminate(nullptr);

const std::string *colors_vec_ptr = p_colors->StringVector()->data();

for (int colors_index = 0; colors_index < n_colors_; ++colors_index)
Eidos_GetColorComponents(colors_vec_ptr[colors_index], red_components_ + colors_index, green_components_ + colors_index, blue_components_ + colors_index);
}
else
{
free(red_components_);
free(green_components_);
free(blue_components_);
red_components_ = nullptr;
green_components_ = nullptr;
blue_components_ = nullptr;
}

_ValuesChanged();
}

void SpatialMap::TakeValuesFromEidosValue(EidosValue *p_values, std::string p_code_name, std::string p_eidos_name)
{
int values_dimcount = p_values->DimensionCount();
Expand Down Expand Up @@ -1036,6 +1040,7 @@ EidosValue_SP SpatialMap::ExecuteInstanceMethod(EidosGlobalStringID p_method_id,
case gID_divide: return ExecuteMethod_divide(p_method_id, p_arguments, p_interpreter);
case gID_power: return ExecuteMethod_power(p_method_id, p_arguments, p_interpreter);
case gID_exp: return ExecuteMethod_exp(p_method_id, p_arguments, p_interpreter);
case gID_changeColors: return ExecuteMethod_changeColors(p_method_id, p_arguments, p_interpreter);
case gID_changeValues: return ExecuteMethod_changeValues(p_method_id, p_arguments, p_interpreter);
case gID_gridValues: return ExecuteMethod_gridValues(p_method_id, p_arguments, p_interpreter);
case gID_interpolate: return ExecuteMethod_interpolate(p_method_id, p_arguments, p_interpreter);
Expand Down Expand Up @@ -1284,6 +1289,19 @@ EidosValue_SP SpatialMap::ExecuteMethod_exp(EidosGlobalStringID p_method_id, con
return EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object_singleton(this, gSLiM_SpatialMap_Class));
}

// ********************* - (void)changeColors([Nif valueRange = NULL], [Ns color = NULL])
//
EidosValue_SP SpatialMap::ExecuteMethod_changeColors(EidosGlobalStringID p_method_id, const std::vector<EidosValue_SP> &p_arguments, EidosInterpreter &p_interpreter)
{
#pragma unused (p_method_id, p_arguments, p_interpreter)
EidosValue *value_range = p_arguments[0].get();
EidosValue *colors = p_arguments[1].get();

TakeColorsFromEidosValues(value_range, colors, "SpatialMap::ExecuteMethod_changeColors", "changeColors()");

return gStaticEidosValueVOID;
}

// ********************* - (void)changeValues(numeric values)
//
EidosValue_SP SpatialMap::ExecuteMethod_changeValues(EidosGlobalStringID p_method_id, const std::vector<EidosValue_SP> &p_arguments, EidosInterpreter &p_interpreter)
Expand Down Expand Up @@ -2228,6 +2246,7 @@ const std::vector<EidosMethodSignature_CSP> *SpatialMap_Class::Methods(void) con
methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_divide, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_SpatialMap_Class))->AddArg(kEidosValueMaskNumeric | kEidosValueMaskObject, "x", gSLiM_SpatialMap_Class));
methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_power, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_SpatialMap_Class))->AddArg(kEidosValueMaskNumeric | kEidosValueMaskObject, "x", gSLiM_SpatialMap_Class));
methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_exp, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_SpatialMap_Class)));
methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_changeColors, kEidosValueMaskVOID))->AddNumeric_ON("valueRange", gStaticEidosValueNULL)->AddString_ON("colors", gStaticEidosValueNULL));
methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_changeValues, kEidosValueMaskVOID))->AddNumeric("values"));
methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_gridValues, kEidosValueMaskFloat)));
methods->emplace_back((EidosInstanceMethodSignature *)(new EidosInstanceMethodSignature(gStr_interpolate, kEidosValueMaskObject | kEidosValueMaskSingleton, gSLiM_SpatialMap_Class))->AddInt_S("factor")->AddString_OS("method", EidosValue_String_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_String_singleton("linear"))));
Expand Down
Loading

0 comments on commit 11be0b2

Please sign in to comment.