Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add projection expression syntax #888

Merged
Show file tree
Hide file tree
Changes from 105 commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
9f0d5ee
Make globe a spherical projection
birkskyum Nov 5, 2024
596c048
work on expression
birkskyum Nov 5, 2024
56a04f3
schema
birkskyum Nov 5, 2024
2611016
example
birkskyum Nov 5, 2024
8c34a52
better example
birkskyum Nov 5, 2024
7bcf2e7
cleanup
birkskyum Nov 5, 2024
931fc9a
work on style projection expression
birkskyum Nov 5, 2024
3b87d0b
projection tests
birkskyum Nov 5, 2024
88422ab
add tests
birkskyum Nov 5, 2024
81b441a
test
birkskyum Nov 5, 2024
3f1a371
test
birkskyum Nov 5, 2024
a569723
proj
birkskyum Nov 5, 2024
949e72c
lint
birkskyum Nov 5, 2024
52343f0
interpolate-projection
birkskyum Nov 5, 2024
dd5af6f
fixes
birkskyum Nov 5, 2024
4c8b8d5
interpolate-projection in validate_projection
birkskyum Nov 5, 2024
d3717bf
update the spec
birkskyum Nov 5, 2024
dc4e463
typing
birkskyum Nov 5, 2024
208afe2
fix types
birkskyum Nov 5, 2024
728469d
lint
birkskyum Nov 5, 2024
f7c458f
work on interpolation
birkskyum Nov 5, 2024
88dda3a
use projectio type
birkskyum Nov 5, 2024
ce97699
lint
birkskyum Nov 5, 2024
f901a50
vork out validateion
birkskyum Nov 6, 2024
efcee2c
fix
birkskyum Nov 6, 2024
6341baf
test
birkskyum Nov 6, 2024
d2d755d
condensend syntax
birkskyum Nov 6, 2024
b80a150
lint
birkskyum Nov 6, 2024
53b098e
difftest
birkskyum Nov 6, 2024
8a95673
reduce diff
birkskyum Nov 6, 2024
9ef606f
docs
birkskyum Nov 6, 2024
c799991
simplify docs
birkskyum Nov 6, 2024
a74a812
docs
birkskyum Nov 6, 2024
695aa90
interpolate type
birkskyum Nov 6, 2024
743282e
use projection class
birkskyum Nov 6, 2024
483c8e0
use projection class
birkskyum Nov 6, 2024
fcbf835
lint
birkskyum Nov 6, 2024
7548e99
lint
birkskyum Nov 6, 2024
87ec0ec
rename
birkskyum Nov 6, 2024
387f092
Update src/expression/definitions/interpolate.ts
HarelM Nov 6, 2024
765c1a9
cleanup
birkskyum Nov 6, 2024
8e4eb9c
cleanup
birkskyum Nov 6, 2024
eed2fc1
simplify step
birkskyum Nov 6, 2024
69e89b7
better spec
birkskyum Nov 6, 2024
ba4a001
projectionType
birkskyum Nov 6, 2024
eaa3f76
type
birkskyum Nov 7, 2024
fda7d78
rename file
birkskyum Nov 7, 2024
408fdc0
type
birkskyum Nov 7, 2024
1c9e17a
validation
birkskyum Nov 7, 2024
6781b91
remove parse
birkskyum Nov 7, 2024
8e6f991
interpolation
birkskyum Nov 7, 2024
edae354
diff
birkskyum Nov 7, 2024
87af425
type
birkskyum Nov 7, 2024
15ec25d
docs
birkskyum Nov 7, 2024
6da6683
work on spec
birkskyum Nov 7, 2024
7151547
docs
birkskyum Nov 7, 2024
6598bad
pull values from spec
birkskyum Nov 7, 2024
3e89e84
spec
birkskyum Nov 7, 2024
8d46eb1
projectionTransition
birkskyum Nov 7, 2024
6432a67
link to projectionTransition
birkskyum Nov 7, 2024
e11e196
spec
birkskyum Nov 7, 2024
fc5b33c
lint
birkskyum Nov 7, 2024
b68e386
diff
birkskyum Nov 7, 2024
9beb898
projectionTransition
birkskyum Nov 7, 2024
969891c
interpolate
birkskyum Nov 7, 2024
fe8e4c7
types
birkskyum Nov 7, 2024
553a260
types
birkskyum Nov 7, 2024
e18f8fa
remove globe preset line
birkskyum Nov 7, 2024
439a9d2
easy readable
birkskyum Nov 7, 2024
feee50f
projectionConfig
birkskyum Nov 7, 2024
6ad8f4f
projection
birkskyum Nov 7, 2024
d1ce5fb
spec
birkskyum Nov 7, 2024
745ec3d
types
birkskyum Nov 7, 2024
d2e39ad
gen docs
birkskyum Nov 7, 2024
9a40ded
validate
birkskyum Nov 7, 2024
9b4d843
interpolate-projection
birkskyum Nov 7, 2024
dda0295
general-perspective
birkskyum Nov 7, 2024
005975e
vertical-perspective
birkskyum Nov 11, 2024
ceb0719
vertical-perspective
birkskyum Nov 12, 2024
277b82f
fix test name
birkskyum Nov 12, 2024
38d9bc9
reduce projectionconfig test
birkskyum Nov 12, 2024
197c39d
Coalesce projection type
birkskyum Nov 12, 2024
31f91ee
rebase
birkskyum Nov 12, 2024
ee5fb2f
cleanup changelog
birkskyum Nov 12, 2024
51c1ae9
add links to native/gljs
birkskyum Nov 12, 2024
70a7f6d
add integration test
birkskyum Nov 12, 2024
d28b9b4
add tests
birkskyum Nov 12, 2024
5897a8f
update types
birkskyum Nov 13, 2024
74337e6
normalize String to string literal in validator
birkskyum Nov 13, 2024
d60f2cc
remove unreachable code
birkskyum Nov 13, 2024
b4a73ef
export default Projection
birkskyum Nov 13, 2024
0d0d787
Remove the values presets/projections
birkskyum Nov 13, 2024
af6cd97
remove line feed
birkskyum Nov 13, 2024
8f3797b
remove Projection as Projection,
birkskyum Nov 13, 2024
b7c3057
cleanup generate-docs
birkskyum Nov 13, 2024
f86e2de
remove spaces
birkskyum Nov 13, 2024
c7dfd0a
remove `0, "globe"`, from interpolate-projection examples
birkskyum Nov 13, 2024
deb0a79
replace 'globe' with "vertical-perspective"
birkskyum Nov 13, 2024
3dbf359
simplify string test
birkskyum Nov 13, 2024
0a7773a
cleanup string test
birkskyum Nov 13, 2024
1ac110f
dont add 0-zoom level on interpolation example
birkskyum Nov 13, 2024
77d042b
remove 0 zoom in example
birkskyum Nov 13, 2024
0c47edd
simplify transition docs
birkskyum Nov 13, 2024
e51272b
add zoom 12 case
birkskyum Nov 13, 2024
6bbb342
fix indentation in example
birkskyum Nov 13, 2024
6c987db
validate projectionConfig
birkskyum Nov 13, 2024
58150bd
fix interpolate checks
birkskyum Nov 13, 2024
a4fd843
ekstra line
birkskyum Nov 13, 2024
dda14b5
explicit checks on switch (this.type.kind) {
birkskyum Nov 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## main

### ✨ Features and improvements
- Add projection type expression syntax ([#888](https://github.com/maplibre/maplibre-style-spec/pull/888))
- _...Add new stuff here..._

### 🐞 Bug fixes
Expand All @@ -9,7 +10,7 @@
## 21.2.0

### ✨ Features and improvements
Add `vertical-perspective` projection ([#890](https://github.com/maplibre/maplibre-style-spec/pull/890))
- Add `vertical-perspective` projection ([#890](https://github.com/maplibre/maplibre-style-spec/pull/890))

## 21.1.0

Expand Down
3 changes: 3 additions & 0 deletions build/generate-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ function typeToMarkdownLink(type: string): string {
case 'promoteid':
return ` [${type}](types.md)`;
case 'color':
case 'projection':
case 'number':
case 'string':
case 'boolean':
Expand All @@ -180,6 +181,8 @@ function typeToMarkdownLink(type: string): string {
case 'paint':
case 'layout':
return ` [${type}](layers.md#${type.toLocaleLowerCase()})`;
case 'projectionconfig':
return ' [projectionConfig](projection.md)';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really needed here?
This is used for the docs to allow link to an object type related to the expressions I think, and I'm not sure projectionConfig can be a return type of an expression, right?

Copy link
Member Author

@birkskyum birkskyum Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an overwrite I made so that the url of the main Projection page doesn't get a /projectionConfig url, which I thought was undesirable, compared to staying with /projection. I can let it generate the /projectionConfig if preferred.

Screenshot 2024-11-13 at 09 04 55

This is the warning I got on mkdocs-build, when it makes the root pages from the v8, before I added it:
WARNING - Doc file 'root.md' contains a link 'projectionConfig.md', but the target is not found among documentation files.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see...
I'm trying to think if there's a way to solve this elegantly, but I can't think of a good idea at the moment.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is a good idea, but in proj4js they use the term ProjectionDefinition for the values related to the projection:
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/proj4/index.d.ts

I'm not sure what I think about it though - this is for the "internal" type, the one used in the expressions, not the root projection type.

default:
// top level types have their own file
return ` [${type}](${type}.md)`;
Expand Down
11 changes: 9 additions & 2 deletions build/generate-style-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ function propertyType(property) {
return 'SkySpecification';
case 'sources':
return '{[_: string]: SourceSpecification}';
case 'projection:':
return 'ProjectionSpecification';
birkskyum marked this conversation as resolved.
Show resolved Hide resolved
case '*':
return 'unknown';
default:
Expand Down Expand Up @@ -122,6 +124,9 @@ fs.writeFileSync('src/types.g.ts',

export type ColorSpecification = string;

export type ProjectionT = [string, string, number];
export type ProjectionSpecification = string | ProjectionT | PropertyValueSpecification<ProjectionT>

export type PaddingSpecification = number | number[];

export type VariableAnchorOffsetCollectionSpecification = Array<string | [number, number]>;
Expand Down Expand Up @@ -203,6 +208,8 @@ export type ExpressionSpecification =
// Ramps, scales, curves
| ['interpolate', InterpolationSpecification, number | ExpressionSpecification,
...(number | number[] | ColorSpecification | ExpressionSpecification)[]] // alternating number and number | number[] | ColorSpecification
| ['interpolate-projection', InterpolationSpecification, number | ExpressionSpecification,
...(number | string)[]] // alternating Projection
| ['interpolate-hcl', InterpolationSpecification, number | ExpressionSpecification,
...(number | ColorSpecification)[]] // alternating number and ColorSpecificaton
| ['interpolate-lab', InterpolationSpecification, number | ExpressionSpecification,
Expand Down Expand Up @@ -317,9 +324,9 @@ ${objectDeclaration('LightSpecification', spec.light)}

${objectDeclaration('SkySpecification', spec.sky)}

${objectDeclaration('TerrainSpecification', spec.terrain)}
${objectDeclaration('ProjectionConfigSpecification', spec.projectionConfig)}
birkskyum marked this conversation as resolved.
Show resolved Hide resolved

${objectDeclaration('ProjectionSpecification', spec.projection)}
${objectDeclaration('TerrainSpecification', spec.terrain)}

${spec.source.map(key => {
let str = objectDeclaration(sourceTypeName(key), spec[key]);
Expand Down
73 changes: 73 additions & 0 deletions docs/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,76 @@ The following example applies 2em padding on top and bottom and 3em padding left
"icon-padding": [2, 3]
}
```

## Projection

The `projection` is used to configure which projection to use for the map.

There are currently two projections implemented.

- `mercator` - [Web Mercator projection](https://en.wikipedia.org/wiki/Web_Mercator_projection)
- `vertical-perspective` - [Vertical Perspective projection](https://en.wikipedia.org/wiki/General_Perspective_projection)

And the following [presets](#use-a-projection-preset)

The `projection` output sent to the renderer is always of the shape:

`[from, to, transition]: [string, string, number]`

- `from` is the projection of lower zoom level
- `to` is the projection of higher zoom level
- `transition` is the interpolation value, going from 0 to 1, with 0 being in the `from` projection, and 1 being in the `to` projection.

In case `from` and `to` are equal, the `transition` will have no effect.

### Examples

#### Step between projection at discrete zoom levels

Use a [`camera expression`](./expressions.md#camera-expressions), to discretely [`step`](./expressions.md#step) between projections at certain zoom levels.


```ts
type: ["step", ["zoom"],
"vertical-perspective",
11, "mercator"
]


output at zoom 10.9: "vertical-perspective"
output at zoom 11.0: "vertical-perspective"
output at zoom 11.1: "mercator"
```

#### Animate between different projections based on zoom level**

Use a [`camera expression`](./expressions.md#camera-expressions), to animate between projections based on zoom, using [`interpolate-projection`](./expressions.md#interpolate-projection) function. The example below will yield an adaptive globe that interpolates from `vertical-perspective` to `mercator` between zoom 10 and 12.

```ts
type: ["interpolate-projection", ["linear"], ["zoom"],
10,"vertical-perspective",
12,"mercator"
]


output at zoom 9.9: "vertical-perspective"
output at zoom 11: ["vertical-perspective", "mercator", 0.5]
output at zoom 12: ["vertical-perspective", "mercator", 1]
output at zoom 12.1: "mercator"
```


#### Provide a `projection`

```ts
type: ["vertical-perspective", "mercator", 0.7]
```

#### Use a projection preset

There are also additional presets that yield commonly used expressions:


| Preset | Full value | Description |
|--------|------------|-------------|
| `globe` | `["interpolate-projection", ["linear"], ["zoom"],`<br>`10, "vertical-perspective", 12, "mercator"]` | Adaptive globe: interpolates from vertical-perspective to mercator projection between zoom levels 10 and 12. |
7 changes: 3 additions & 4 deletions src/diff.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -606,11 +606,10 @@ describe('diff', () => {
expect(diffStyles({
} as StyleSpecification,
{
projection: {
type: 'globe'
}
projection: {type: ['vertical-perspective', 'mercator', 0.5]}

} as StyleSpecification)).toEqual([
{command: 'setProjection', args: [{type: 'globe'}]},
{command: 'setProjection', args: [{type: ['vertical-perspective', 'mercator', 0.5]}]},
]);
});
});
4 changes: 2 additions & 2 deletions src/diff.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import {GeoJSONSourceSpecification, LayerSpecification, LightSpecification, ProjectionSpecification, SkySpecification, SourceSpecification, SpriteSpecification, StyleSpecification, TerrainSpecification, TransitionSpecification} from './types.g';
import {GeoJSONSourceSpecification, LayerSpecification, LightSpecification, ProjectionConfigSpecification, SkySpecification, SourceSpecification, SpriteSpecification, StyleSpecification, TerrainSpecification, TransitionSpecification} from './types.g';
import isEqual from './util/deep_equal';

/**
Expand Down Expand Up @@ -30,7 +30,7 @@ export type DiffOperationsMap = {
'setLight': [LightSpecification];
'setTerrain': [TerrainSpecification];
'setSky': [SkySpecification];
'setProjection': [ProjectionSpecification];
'setProjection': [ProjectionConfigSpecification];
}

export type DiffOperations = keyof DiffOperationsMap;
Expand Down
1 change: 1 addition & 0 deletions src/expression/definitions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const expressions: ExpressionRegistry = {
'interpolate': Interpolate,
'interpolate-hcl': Interpolate,
'interpolate-lab': Interpolate,
'interpolate-projection': Interpolate,
'length': Length,
'let': Let,
'literal': Literal,
Expand Down
17 changes: 12 additions & 5 deletions src/expression/definitions/interpolate.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import UnitBezier from '@mapbox/unitbezier';

import interpolate from '../../util/interpolate';
import {array, ArrayType, ColorType, ColorTypeT, NumberType, NumberTypeT, PaddingType, PaddingTypeT, VariableAnchorOffsetCollectionType, VariableAnchorOffsetCollectionTypeT, toString, verifyType} from '../types';
import {array, ArrayType, ColorType, ProjectionTypeT, ColorTypeT, NumberType, NumberTypeT, PaddingType, PaddingTypeT, VariableAnchorOffsetCollectionType, VariableAnchorOffsetCollectionTypeT, toString, verifyType, ProjectionType} from '../types';
import {findStopLessThanOrEqualTo} from '../stops';

import type {Stops} from '../stops';
Expand All @@ -19,18 +19,18 @@ export type InterpolationType = {
name: 'cubic-bezier';
controlPoints: [number, number, number, number];
};
type InterpolatedValueType = NumberTypeT | ColorTypeT | PaddingTypeT | VariableAnchorOffsetCollectionTypeT | ArrayType<NumberTypeT>;

type InterpolatedValueType = NumberTypeT | ColorTypeT | ProjectionTypeT | PaddingTypeT | VariableAnchorOffsetCollectionTypeT | ArrayType<NumberTypeT>;
type InterpolationOperator = 'interpolate' | 'interpolate-hcl' | 'interpolate-lab' | 'interpolate-projection';
class Interpolate implements Expression {
type: InterpolatedValueType;

operator: 'interpolate' | 'interpolate-hcl' | 'interpolate-lab';
operator: InterpolationOperator ;
interpolation: InterpolationType;
input: Expression;
labels: Array<number>;
outputs: Array<Expression>;

constructor(type: InterpolatedValueType, operator: 'interpolate' | 'interpolate-hcl' | 'interpolate-lab', interpolation: InterpolationType, input: Expression, stops: Stops) {
constructor(type: InterpolatedValueType, operator: InterpolationOperator, interpolation: InterpolationType, input: Expression, stops: Stops) {
this.type = type;
this.operator = operator;
this.interpolation = interpolation;
Expand Down Expand Up @@ -108,6 +108,8 @@ class Interpolate implements Expression {
let outputType: Type = null;
if (operator === 'interpolate-hcl' || operator === 'interpolate-lab') {
outputType = ColorType;
} else if (operator === 'interpolate-projection') {
outputType = ProjectionType;
} else if (context.expectedType && context.expectedType.kind !== 'value') {
outputType = context.expectedType;
}
Expand Down Expand Up @@ -135,6 +137,7 @@ class Interpolate implements Expression {

if (!verifyType(outputType, NumberType) &&
!verifyType(outputType, ColorType) &&
!verifyType(outputType, ProjectionType) &&
!verifyType(outputType, PaddingType) &&
!verifyType(outputType, VariableAnchorOffsetCollectionType) &&
!verifyType(outputType, array(NumberType))
Expand Down Expand Up @@ -170,6 +173,10 @@ class Interpolate implements Expression {

const outputLower = outputs[index].evaluate(ctx);
const outputUpper = outputs[index + 1].evaluate(ctx);

if (this.type.kind == 'projection') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be added to the below switch-case?

Copy link
Member Author

@birkskyum birkskyum Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A type guard on this.type.kind !== 'projection' is needed, so it turns the switch into this which I think looked worse:

        switch (this.operator) {
            case 'interpolate':
                if (this.type.kind !== 'projection') {
                    return interpolate[this.type.kind](outputLower, outputUpper, t);
                }
            case 'interpolate-hcl':
                return interpolate.color(outputLower, outputUpper, t, 'hcl');
            case 'interpolate-lab':
                return interpolate.color(outputLower, outputUpper, t, 'lab');
            case 'interpolate-projection': {
                return interpolate.projection(outputLower, outputUpper, t);
            }
        }
    ```

Copy link
Member Author

@birkskyum birkskyum Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I'm dealing with

Screenshot 2024-11-13 at 08 44 34

It's been a problem since 'projection' was added as a possible value to .kind:
Screenshot 2024-11-13 at 08 45 18

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stupid part is that this, or insertion of any of the other possible string values for kind, doesn't have a type problem

Screenshot 2024-11-13 at 08 51 59

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case 'interpolate':
      return interpolate[this.type.kind.toString()](outputLower, outputUpper, t);
  case 'interpolate-hcl':
      return interpolate.color(outputLower, outputUpper, t, 'hcl');

A kind.toString() resolves it

Copy link
Collaborator

@HarelM HarelM Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this removes the type guard from the interpolate object, which I'm not sure is what we want.
I generally try to avoid using object[property] as much as possible due to these limitations and the fact that it's harder to read the code this way.

Copy link
Member Author

@birkskyum birkskyum Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an option too, that doesn't ignore any types

        switch (this.operator) {
            case 'interpolate':
                switch (this.type.kind) {
                    case 'number':
                        return interpolate.number(outputLower, outputUpper, t);
                    case 'color':
                        return interpolate.color(outputLower, outputUpper, t, 'rgb');
                    case 'array':
                        return interpolate.array(outputLower, outputUpper, t);
                    case 'padding':
                        return interpolate.padding(outputLower, outputUpper, t);
                    case 'variableAnchorOffsetCollection':
                        return interpolate.variableAnchorOffsetCollection(outputLower, outputUpper, t);
                }
            case 'interpolate-hcl':
                return interpolate.color(outputLower, outputUpper, t, 'hcl');
            case 'interpolate-lab':
                return interpolate.color(outputLower, outputUpper, t, 'lab');
            case 'interpolate-projection': {
                return interpolate.projection(outputLower, outputUpper, t);
            }
        }

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this one better to be honest.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, if this is possible for other types, why do we need an extra case for interpolate-projection then?

return interpolate.projection(outputLower, outputUpper, t);
}

switch (this.operator) {
case 'interpolate':
Expand Down
3 changes: 2 additions & 1 deletion src/expression/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ function findZoomCurve(expression: Expression): Step | Interpolate | ExpressionP
return result;
}

import {ColorType, StringType, NumberType, BooleanType, ValueType, FormattedType, PaddingType, ResolvedImageType, VariableAnchorOffsetCollectionType, array} from './types';
import {ColorType, StringType, NumberType, BooleanType, ValueType, FormattedType, PaddingType, ResolvedImageType, VariableAnchorOffsetCollectionType, array, ProjectionType} from './types';
import Padding from '../util/padding';
import {ICanonicalTileID} from '../tiles_and_coordinates';

Expand All @@ -453,6 +453,7 @@ function getExpectedType(spec: StylePropertySpecification): Type {
boolean: BooleanType,
formatted: FormattedType,
padding: PaddingType,
projection: ProjectionType,
resolvedImage: ResolvedImageType,
variableAnchorOffsetCollection: VariableAnchorOffsetCollectionType
};
Expand Down
2 changes: 2 additions & 0 deletions src/expression/parsing_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ class ParsingContext {
//
if ((expected.kind === 'string' || expected.kind === 'number' || expected.kind === 'boolean' || expected.kind === 'object' || expected.kind === 'array') && actual.kind === 'value') {
parsed = annotate(parsed, expected, options.typeAnnotation || 'assert');
} else if ((expected.kind === 'projection') && (actual.kind === 'string')) {
parsed = annotate(parsed, expected, options.typeAnnotation || 'coerce');
} else if ((expected.kind === 'color' || expected.kind === 'formatted' || expected.kind === 'resolvedImage') && (actual.kind === 'value' || actual.kind === 'string')) {
parsed = annotate(parsed, expected, options.typeAnnotation || 'coerce');
} else if (expected.kind === 'padding' && (actual.kind === 'value' || actual.kind === 'number' || actual.kind === 'array')) {
Expand Down
7 changes: 6 additions & 1 deletion src/expression/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export type BooleanTypeT = {
export type ColorTypeT = {
kind: 'color';
};
export type ProjectionTypeT = {
kind: 'projection';
};
export type ObjectTypeT = {
kind: 'object';
};
Expand Down Expand Up @@ -40,7 +43,7 @@ export type VariableAnchorOffsetCollectionTypeT = {

export type EvaluationKind = 'constant' | 'source' | 'camera' | 'composite';

export type Type = NullTypeT | NumberTypeT | StringTypeT | BooleanTypeT | ColorTypeT | ObjectTypeT | ValueTypeT |
export type Type = NullTypeT | NumberTypeT | StringTypeT | BooleanTypeT | ColorTypeT | ProjectionTypeT | ObjectTypeT | ValueTypeT |
ArrayType | ErrorTypeT | CollatorTypeT | FormattedTypeT | PaddingTypeT | ResolvedImageTypeT | VariableAnchorOffsetCollectionTypeT;

export interface ArrayType<T extends Type = Type> {
Expand All @@ -56,6 +59,7 @@ export const NumberType = {kind: 'number'} as NumberTypeT;
export const StringType = {kind: 'string'} as StringTypeT;
export const BooleanType = {kind: 'boolean'} as BooleanTypeT;
export const ColorType = {kind: 'color'} as ColorTypeT;
export const ProjectionType = {kind: 'projection'} as ProjectionTypeT;
export const ObjectType = {kind: 'object'} as ObjectTypeT;
export const ValueType = {kind: 'value'} as ValueTypeT;
export const ErrorType = {kind: 'error'} as ErrorTypeT;
Expand Down Expand Up @@ -90,6 +94,7 @@ const valueMemberTypes = [
StringType,
BooleanType,
ColorType,
ProjectionType,
FormattedType,
ObjectType,
array(ValueType),
Expand Down
12 changes: 8 additions & 4 deletions src/expression/values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import Formatted from './types/formatted';
import Padding from '../util/padding';
import VariableAnchorOffsetCollection from '../util/variable_anchor_offset_collection';
import ResolvedImage from './types/resolved_image';
import {NullType, NumberType, StringType, BooleanType, ColorType, ObjectType, ValueType, CollatorType, FormattedType, ResolvedImageType, array, PaddingType, VariableAnchorOffsetCollectionType} from './types';
import {NullType, NumberType, StringType, BooleanType, ColorType, ObjectType, ValueType, CollatorType, FormattedType, ResolvedImageType, array, PaddingType, VariableAnchorOffsetCollectionType, ProjectionType} from './types';

import type {Type} from './types';
import Projection from '../util/projection';

export function validateRGBA(r: unknown, g: unknown, b: unknown, a?: unknown): string | null {
if (!(
Expand All @@ -28,7 +29,7 @@ export function validateRGBA(r: unknown, g: unknown, b: unknown, a?: unknown): s
return null;
}

export type Value = null | string | boolean | number | Color | Collator | Formatted | Padding | ResolvedImage | VariableAnchorOffsetCollection | ReadonlyArray<Value> | {
export type Value = null | string | boolean | number | Color | Projection | Collator | Formatted | Padding | ResolvedImage | VariableAnchorOffsetCollection | ReadonlyArray<Value> | {
readonly [x: string]: Value;
};

Expand All @@ -37,6 +38,7 @@ export function isValue(mixed: unknown): boolean {
typeof mixed === 'string' ||
typeof mixed === 'boolean' ||
typeof mixed === 'number' ||
mixed instanceof Projection ||
mixed instanceof Color ||
mixed instanceof Collator ||
mixed instanceof Formatted ||
Expand Down Expand Up @@ -74,6 +76,8 @@ export function typeOf(value: Value): Type {
return NumberType;
} else if (value instanceof Color) {
return ColorType;
} else if (value instanceof Projection) {
return ProjectionType;
} else if (value instanceof Collator) {
return CollatorType;
} else if (value instanceof Formatted) {
Expand Down Expand Up @@ -112,11 +116,11 @@ export function toString(value: Value) {
return '';
} else if (type === 'string' || type === 'number' || type === 'boolean') {
return String(value);
} else if (value instanceof Color || value instanceof Formatted || value instanceof Padding || value instanceof VariableAnchorOffsetCollection || value instanceof ResolvedImage) {
} else if (value instanceof Color || value instanceof Projection || value instanceof Formatted || value instanceof Padding || value instanceof VariableAnchorOffsetCollection || value instanceof ResolvedImage) {
return value.toString();
} else {
return JSON.stringify(value);
}
}

export {Color, Collator, Padding, VariableAnchorOffsetCollection};
export {Color, Collator, Projection, Padding, VariableAnchorOffsetCollection};
3 changes: 2 additions & 1 deletion src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ function validSchema(k, v, obj, ref, version, kind) {
'promoteId',
'padding',
'variableAnchorOffsetCollection',
'sprite'
'sprite',
'projection'
]);
const keys = [
'default',
Expand Down
Loading