• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

iTowns / itowns / 15302205421

28 May 2025 02:02PM UTC coverage: 87.096% (-0.02%) from 87.111%
15302205421

push

github

web-flow
refactor(LayeredMaterial): migrate to TypeScript (#2477)

feat(LayeredMaterial): migrate to TypeScript

Squashed commits (oldest first):
- fix(tests): add new empty methods to mock Material
- refactor: use interface where possible
- fix: use setUniform for material showOutline set
- fix(elevation): correct antilogy
- refactor: rename layer to tile where relevant
- fix(geoidlayer): revert wrong uniform access change
- fix(InfoLayer): use new LayeredMaterial API
- fix(TileMesh): properly set objectId
- refactor: apply some of the suggested changes
- fix: replace mock material in tilemesh tests
- fix(RasterTile): fix RasterTile creation calls

2805 of 3747 branches covered (74.86%)

Branch coverage included in aggregate %.

509 of 581 new or added lines in 14 files covered. (87.61%)

11 existing lines in 2 files now uncovered.

26002 of 29328 relevant lines covered (88.66%)

1104.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.52
/packages/Main/src/Process/LayeredMaterialNodeProcessing.js
1
import { chooseNextLevelToFetch } from 'Layer/LayerUpdateStrategy';
1✔
2
import LayerUpdateState from 'Layer/LayerUpdateState';
1✔
3
import handlingError from 'Process/handlerNodeError';
1✔
4

1✔
5
function materialCommandQueuePriorityFunction(material) {
9✔
6
    // We know that 'node' is visible because commands can only be
9✔
7
    // issued for visible nodes.
9✔
8
    // TODO: need priorization of displayed nodes
9✔
9
    // Then prefer displayed node over non-displayed one
9✔
10
    return material.visible ? 100 : 10;
9!
11
}
9✔
12

1✔
13
function refinementCommandCancellationFn(cmd) {
1✔
14
    if (!cmd.requester.parent || !cmd.requester.material) {
1!
15
        return true;
×
16
    }
×
17
    // Cancel the command if the tile already has a better texture.
1✔
18
    // This is only needed for elevation layers, because we may have several
1✔
19
    // concurrent layers but we can only use one texture.
1✔
20
    if (cmd.layer.isElevationLayer && cmd.requester.material.getElevationTile() &&
1!
21
        cmd.targetLevel <= cmd.requester.material.getElevationTile().level) {
1!
22
        return true;
×
23
    }
×
24

1✔
25
    // Cancel the command if the layer was removed between command scheduling and command execution
1✔
26
    if (!cmd.requester.layerUpdateState[cmd.layer.id]
1✔
27
        || !cmd.layer.source._featuresCaches[cmd.layer.crs]) {
1!
28
        return true;
×
29
    }
×
30

1✔
31
    return !cmd.requester.material.visible;
1✔
32
}
1✔
33

1✔
34
function buildCommand(view, layer, extentsSource, extentsDestination, requester) {
9✔
35
    return {
9✔
36
        view,
9✔
37
        layer,
9✔
38
        extentsSource,
9✔
39
        extentsDestination,
9✔
40
        requester,
9✔
41
        priority: materialCommandQueuePriorityFunction(requester.material),
9✔
42
        earlyDropFunction: refinementCommandCancellationFn,
9✔
43
        partialLoading: true,
9✔
44
    };
9✔
45
}
9✔
46

1✔
47
function computePitchs(textures, extentsDestination) {
1✔
48
    return extentsDestination
1✔
49
        .map((ext, i) => (ext.offsetToParent(textures[i].extent)));
1✔
50
}
1✔
51

1✔
52
export function updateLayeredMaterialNodeImagery(context, layer, node, parent) {
1✔
53
    const material = node.material;
15✔
54
    if (!parent || !material) {
15!
55
        return;
×
56
    }
×
57
    const extentsDestination = node.getExtentsByProjection(layer.crs);
15✔
58

15✔
59
    const zoom = extentsDestination[0].zoom;
15✔
60
    if (zoom > layer.zoom.max || zoom < layer.zoom.min) {
15!
61
        return;
×
62
    }
×
63

15✔
64
    let nodeLayer = material.getTile(layer.id);
15✔
65

15✔
66
    // Initialisation
15✔
67
    if (node.layerUpdateState[layer.id] === undefined) {
15✔
68
        node.layerUpdateState[layer.id] = new LayerUpdateState();
9✔
69

9✔
70
        if (!layer.source.extentInsideLimit(node.extent, zoom)) {
9!
71
            // we also need to check that tile's parent doesn't have a texture for this layer,
×
72
            // because even if this tile is outside of the layer, it could inherit it's
×
73
            // parent texture
×
74
            if (!layer.noTextureParentOutsideLimit &&
×
75
                parent.material &&
×
NEW
76
                parent.material.getTile &&
×
NEW
77
                parent.material.getTile(layer.id)) {
×
78
                // ok, we're going to inherit our parent's texture
×
79
            } else {
×
80
                node.layerUpdateState[layer.id].noMoreUpdatePossible();
×
81
                return;
×
82
            }
×
83
        }
×
84

9✔
85
        if (!nodeLayer) {
9✔
86
            // Create new raster node
2✔
87
            nodeLayer = layer.setupRasterNode(node);
2✔
88

2✔
89
            // Init the node by parent
2✔
90
            const parentLayer = parent.material?.getTile(layer.id);
2!
91
            nodeLayer.initFromParent(parentLayer, extentsDestination);
2✔
92
        }
2✔
93

9✔
94
        // Proposed new process, two separate processes:
9✔
95
        //      * FIRST PASS: initNodeXXXFromParent and get out of the function
9✔
96
        //      * SECOND PASS: Fetch best texture
9✔
97

9✔
98
        // The two-step allows you to filter out unnecessary requests
9✔
99
        // Indeed in the second pass, their state (not visible or not displayed) can block them to fetch
9✔
100
        if (nodeLayer.level >= layer.source.zoom.min) {
9✔
101
            context.view.notifyChange(node, false);
4✔
102
            return;
4✔
103
        }
4✔
104
    }
9✔
105

11✔
106
    // Node is hidden, no need to update it
11✔
107
    if (!material.visible) {
15!
108
        return;
×
109
    }
✔
110

11✔
111
    // An update is pending / or impossible -> abort
11✔
112
    if (!layer.visible || !node.layerUpdateState[layer.id].canTryUpdate()) {
15✔
113
        return;
4✔
114
    }
4✔
115

7✔
116
    if (nodeLayer.level >= extentsDestination[0].zoom) {
15!
117
        // default decision method
×
118
        node.layerUpdateState[layer.id].noMoreUpdatePossible();
×
119
        return;
×
120
    }
✔
121

7✔
122
    // is fetching data from this layer disabled?
7✔
123
    if (layer.frozen) {
15!
124
        return;
×
125
    }
✔
126

7✔
127
    const failureParams = node.layerUpdateState[layer.id].failureParams;
7✔
128
    const destinationLevel = extentsDestination[0].zoom || node.level;
15!
129
    const targetLevel = chooseNextLevelToFetch(layer.updateStrategy.type, node, destinationLevel, nodeLayer.level, layer, failureParams);
15✔
130

15✔
131
    if ((!layer.source.isVectorSource && targetLevel <= nodeLayer.level) || targetLevel > destinationLevel) {
15!
132
        if (failureParams.lowestLevelError != Infinity) {
×
133
            // this is the highest level found in case of error.
×
134
            node.layerUpdateState[layer.id].noMoreUpdatePossible();
×
135
        }
×
136
        return;
×
137
    } else if (!layer.source.extentInsideLimit(node.extent, targetLevel)) {
15!
138
        node.layerUpdateState[layer.id].noData({ targetLevel });
×
139
        context.view.notifyChange(node, false);
×
140
        return;
×
141
    }
×
142

7✔
143
    const extentsSource = extentsDestination.map(e => e.tiledExtentParent(targetLevel));
7✔
144
    node.layerUpdateState[layer.id].newTry();
7✔
145
    const command = buildCommand(context.view, layer, extentsSource, extentsDestination, node);
7✔
146

7✔
147
    return context.scheduler.execute(command).then(
7✔
148
        (results) => {
7✔
149
            // Does nothing if the layer has been removed while command was being or waiting to be executed
×
150
            if (!node.layerUpdateState[layer.id]) {
×
151
                return;
×
152
            }
×
153
            const textures = results.map((texture, index) => (texture != null ? texture :
×
154
                { isTexture: false, extent: extentsDestination[index] }));
×
155
            // TODO: Handle error : result is undefined in provider. throw error
×
156
            const pitchs = computePitchs(textures, extentsDestination);
×
157
            nodeLayer.setTextures(textures, pitchs);
×
158
            node.layerUpdateState[layer.id].success();
×
159
        },
7✔
160
        err => handlingError(err, node, layer, targetLevel, context.view));
7✔
161
}
7✔
162

1✔
163
export function updateLayeredMaterialNodeElevation(context, layer, node, parent) {
1✔
164
    const material = node.material;
3✔
165
    if (!parent || !material) {
3!
166
        return;
×
167
    }
×
168

3✔
169
    // TODO: we need either
3✔
170
    //  - compound or exclusive layers
3✔
171
    //  - support for multiple elevation layers
3✔
172

3✔
173
    // Elevation is currently handled differently from color layers.
3✔
174
    // This is caused by a LayeredMaterial limitation: only 1 elevation texture
3✔
175
    // can be used (where a tile can have N textures x M layers)
3✔
176
    const extentsDestination = node.getExtentsByProjection(layer.crs);
3✔
177
    const zoom = extentsDestination[0].zoom;
3✔
178
    if (zoom > layer.zoom.max || zoom < layer.zoom.min) {
3!
179
        return;
×
180
    }
×
181
    // Init elevation layer, and inherit from parent if possible
3✔
182
    let nodeLayer = material.getElevationTile();
3✔
183
    if (!nodeLayer) {
3!
184
        nodeLayer = layer.setupRasterNode(node);
×
185
    }
×
186

3✔
187
    if (node.layerUpdateState[layer.id] === undefined) {
3✔
188
        node.layerUpdateState[layer.id] = new LayerUpdateState();
1✔
189

1✔
190
        const parentLayer = parent.material?.getTile(layer.id);
1!
191
        nodeLayer.initFromParent(parentLayer, extentsDestination);
1✔
192

1✔
193
        if (nodeLayer.level >= layer.source.zoom.min) {
1!
194
            context.view.notifyChange(node, false);
×
195
            return;
×
196
        }
×
197
    }
1✔
198

3✔
199
    // Possible conditions to *not* update the elevation texture
3✔
200
    if (layer.frozen ||
3✔
201
        !material.visible ||
3✔
202
        !node.layerUpdateState[layer.id].canTryUpdate()) {
3✔
203
        return;
1✔
204
    }
1✔
205

2✔
206
    const failureParams = node.layerUpdateState[layer.id].failureParams;
2✔
207
    const targetLevel = chooseNextLevelToFetch(layer.updateStrategy.type, node, extentsDestination[0].zoom, nodeLayer.level, layer, failureParams);
2✔
208

2✔
209
    if (targetLevel <= nodeLayer.level || targetLevel > extentsDestination[0].zoom) {
3!
210
        node.layerUpdateState[layer.id].noMoreUpdatePossible();
×
211
        return;
×
212
    } else if (!layer.source.extentInsideLimit(node.extent, targetLevel)) {
3!
213
        node.layerUpdateState[layer.id].noData({ targetLevel });
×
214
        context.view.notifyChange(node, false);
×
215
        return;
×
216
    }
×
217

2✔
218
    const extentsSource = extentsDestination.map(e => e.tiledExtentParent(targetLevel));
2✔
219
    node.layerUpdateState[layer.id].newTry();
2✔
220
    const command = buildCommand(context.view, layer, extentsSource, extentsDestination, node);
2✔
221

2✔
222
    return context.scheduler.execute(command).then(
2✔
223
        (results) => {
2✔
224
            // Does nothing if the layer has been removed while command was being or waiting to be executed
1✔
225
            if (!node.layerUpdateState[layer.id]) {
1!
226
                return;
×
227
            }
×
228

1✔
229
            // Do not apply the new texture if its level is < than the current
1✔
230
            // one.  This is only needed for elevation layers, because we may
1✔
231
            // have several concurrent layers but we can only use one texture.
1✔
232
            if (targetLevel <= nodeLayer.level) {
1!
233
                node.layerUpdateState[layer.id].noMoreUpdatePossible();
×
234
                return;
×
235
            }
×
236
            const pitchs = computePitchs(results, extentsDestination);
1✔
237
            nodeLayer.setTextures(results, pitchs);
1✔
238
            node.layerUpdateState[layer.id].success();
1✔
239
        },
2✔
240
        err => handlingError(err, node, layer, targetLevel, context.view));
2✔
241
}
2✔
242

1✔
243
export function removeLayeredMaterialNodeTile(tileId) {
1✔
244
    /**
×
245
     * @param {TileMesh} node - The node to udpate.
×
246
     */
×
NEW
247
    return function removeLayeredMaterialNodeTile(node) {
×
NEW
248
        if (node.material?.removeTile) {
×
NEW
249
            if (node.material.elevationTile !== undefined) {
×
250
                node.setBBoxZ({ min: 0, max: 0 });
×
251
            }
×
NEW
252
            node.material.removeTile(tileId);
×
253
        }
×
NEW
254
        if (node.layerUpdateState && node.layerUpdateState[tileId]) {
×
NEW
255
            delete node.layerUpdateState[tileId];
×
256
        }
×
257
    };
×
258
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc