Skip to content

Commit

Permalink
Allow Expressions to be set for LABEL properties using MapScript (#6904)
Browse files Browse the repository at this point in the history
This pull request allows the new functionality in #6884 of using an expression in a LABEL PRIORITY in MapScript. 
It takes a similar approach to setting attribute bindings for properties, which requires using the appropriate attribute constant such as `MS_LABEL_BINDING_PRIORITY`:

```python
label = mapscript.labelObj()
label .setExpressionBinding(mapscript.MS_LABEL_BINDING_PRIORITY, "[MY_ATTRIBUTE] * 2")
```

This is a little clunky but is at least consistent with setting attribute bindings:

```python
label = mapscript.labelObj()
label.setBinding(mapscript.MS_LABEL_BINDING_COLOR, "NEW_BINDING")
```

While testing this I noticed that the `writeLabel` function used to write Mapfile objects ignored expressions. This seems an oversight also for the `LABEL` `SIZE` expression. If this approach seems appropriate I can use this for other expression bindings.

Something to note is that a property could have an attribute binding **and** an expression binding set. In this case the attribute binding currently takes precedence when calculating a shape value, so it also takes precedence when writing to a Mapfile. 

In theory setting an expression could also set the attribute binding to NULL. This is not currently the case for MapScript or when reading a Mapfile twice.
  • Loading branch information
geographika committed Jun 22, 2023
1 parent 94c1e5a commit ac60539
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 1 deletion.
2 changes: 2 additions & 0 deletions mapfile.c
Expand Up @@ -2022,6 +2022,8 @@ static void writeLabel(FILE *stream, int indent, labelObj *label)

if(label->numbindings > 0 && label->bindings[MS_LABEL_BINDING_PRIORITY].item)
writeAttributeBinding(stream, indent, "PRIORITY", &(label->bindings[MS_LABEL_BINDING_PRIORITY]));
else if (label->nexprbindings > 0 && label->exprBindings[MS_LABEL_BINDING_PRIORITY].string)
writeExpression(stream, indent, "PRIORITY", &(label->exprBindings[MS_LABEL_BINDING_PRIORITY]));
else writeNumber(stream, indent, "PRIORITY", MS_DEFAULT_LABEL_PRIORITY, label->priority);

writeNumber(stream, indent, "REPEATDISTANCE", 0, label->repeatdistance);
Expand Down
11 changes: 11 additions & 0 deletions mapscript/python/tests/cases/label_test.py
Expand Up @@ -38,6 +38,17 @@ def testLabelBinding(self):
new_label.setBinding(mapscript.MS_LABEL_BINDING_COLOR, "NEW_BINDING")
assert (new_label.getBinding(mapscript.MS_LABEL_BINDING_COLOR) == "NEW_BINDING")

def testLabelExpressionBinding(self):
"""expression binding can be set and get"""
new_label = mapscript.labelObj()
assert (not new_label.getExpressionBinding(mapscript.MS_LABEL_BINDING_PRIORITY))
new_label.setExpressionBinding(mapscript.MS_LABEL_BINDING_PRIORITY, "[MY_ATTRIBUTE] * 2")
assert (new_label.getExpressionBinding(mapscript.MS_LABEL_BINDING_PRIORITY) == "([MY_ATTRIBUTE] * 2)")

exp = 'LABEL\n SIZE 10\n OFFSET 0 0\n POSITION CC\n ' \
'PRIORITY ([MY_ATTRIBUTE] * 2)\n SHADOWSIZE 1 1\nEND # LABEL\n'
assert (new_label.convertToString() == exp)


class LabelCacheMemberTestCase(MapTestCase):

Expand Down
47 changes: 46 additions & 1 deletion mapscript/swiginc/label.i
Expand Up @@ -112,7 +112,52 @@
return MS_SUCCESS;
}

/// Set the label expression.
/// Remove an expression binding for a specified label property.
int removeExpressionBinding(int binding)
{
if(binding < 0 || binding >= MS_LABEL_BINDING_LENGTH) return MS_FAILURE;

if(self->exprBindings[binding].string) {
msFreeExpression(&self->exprBindings[binding]);
self->nexprbindings--;
}

return MS_SUCCESS;
}

/// Get the expression binding for a specified label property. Returns NULL if there is no binding for this property.
%newobject getExpressionBinding;
const char *getExpressionBinding(int binding)
{
if(binding < 0 || binding >= MS_LABEL_BINDING_LENGTH) return NULL;

return msGetExpressionString(&(self->exprBindings[binding]));
}

/// Set the expression binding for a specified label property. Binding constants look like this: ``MS_LABEL_BINDING_[attribute name]``
/// Expressions are automatically wrapped in brackets, so do not need to be added to the input string
/// >>> new_label.setExpressionBinding(MS_LABEL_BINDING_PRIORITY, "[priority] * 2")
int setExpressionBinding(int binding, const char *text)
{
if (!text || strlen(text) == 0) {
return MS_FAILURE;
}

if(binding < 0 || binding >= MS_LABEL_BINDING_LENGTH) return MS_FAILURE;

if(self->exprBindings[binding].string) {
msFreeExpression(&self->exprBindings[binding]);
self->nexprbindings--;
}

self->exprBindings[binding].string = msStrdup(text);
self->exprBindings[binding].type = MS_EXPRESSION;
self->nexprbindings++;

return MS_SUCCESS;
}

/// Set the label expression property
int setExpression(char *expression)
{
if (!expression || strlen(expression) == 0) {
Expand Down

0 comments on commit ac60539

Please sign in to comment.