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

iTowns / itowns / 6979781676

24 Nov 2023 10:44AM UTC coverage: 77.111% (+0.1%) from 77.004%
6979781676

Pull #2223

github

web-flow
Merge 23836a3b7 into 1d10290b5
Pull Request #2223: Fix base alti for mesh 3d

4051 of 5992 branches covered (0.0%)

Branch coverage included in aggregate %.

216 of 238 new or added lines in 9 files covered. (90.76%)

9 existing lines in 4 files now uncovered.

7986 of 9618 relevant lines covered (83.03%)

1640.25 hits per line

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

74.27
/src/Core/Label.js
1
import * as THREE from 'three';
1✔
2
import DEMUtils from 'Utils/DEMUtils';
1✔
3
import Coordinates from 'Core/Geographic/Coordinates';
1,667✔
4

5
const coord = new Coordinates('EPSG:4326');
1✔
6
let rect;
7

8
// set it once
9
let STYLE_TRANSFORM = '';
1✔
10
if (document.documentElement.style.transform !== undefined) {
1!
11
    STYLE_TRANSFORM = 'transform';
×
12
} else if (document.documentElement.style.webkitTransform !== undefined) {
1!
13
    STYLE_TRANSFORM = 'webkitTransform';
×
14
} else if (document.documentElement.style.mozTransform !== undefined) {
1!
15
    STYLE_TRANSFORM = 'mozTransform';
×
16
} else if (document.documentElement.style.oTransform !== undefined) {
1!
17
    STYLE_TRANSFORM = 'oTransform';
×
18
} else {
19
    STYLE_TRANSFORM = 'transform';
1✔
20
}
21

22
/**
23
 * An object that handles the display of a text and/or an icon, linked to a 3D
24
 * position. The content of the `Label` is managed through a DOM object, in a
25
 * `<div>` handled by the `Label2DRenderer`.
26
 *
27
 * @property {boolean} isLabel - Used to checkout whether this object is a
28
 * Label. Default is true. You should not change this, as it is used internally
29
 * for optimisation.
30
 * @property {Element} content - The DOM object that contains the content of the
31
 * label. The style and the position are applied on this object. All labels
32
 * contain the `itowns-label` class, as well as a specific class related to the
33
 * layer linked to it: `itowns-label-[layer-id]` (replace `[layer-id]` by the
34
 * correct string).
35
 * @property {THREE.Vector3} position - The position in the 3D world of the
36
 * label.
37
 * @property {number} padding - sets the padding area on all four sides of an element at once.
38
 * @property {Coordinates} coordinates - The coordinates of the label.
39
 * @property {number} order - Order of the label that will be read from the
40
 * style. It helps sorting and prioritizing a Label during render.
41
 */
42
class Label extends THREE.Object3D {
2✔
43
    /**
44
     * @param {Element|string} content - The content; can be a
45
     * string, with or without HTML tags in it, or it can be an Element.
46
     * @param {Coordinates} coordinates - The world coordinates, where to place
47
     * the Label.
48
     * @param {Style} style - The style to apply to the content. Once the style
49
     * is applied, it cannot be changed directly. However, if it really needed,
50
     * it can be accessed through `label.content.style`, but it is highly
51
     * discouraged to do so.
52
     */
53
    constructor(content = '', coordinates, style = {}) {
11✔
54
        if (coordinates == undefined) {
11✔
55
            throw new Error('coordinates are mandatory to add a Label');
2✔
56
        }
57
        if (arguments.length > 3) {
9!
58
            console.warn('Deprecated argument sprites in Label constructor. Sprites must be configured in style argument.');
×
59
        }
60

61
        super();
9✔
62

63
        let _visible = this.visible;
9✔
64
        // can't do an ES6 setter/getter here
65
        Object.defineProperty(this, 'visible', {
9✔
66
            set(v) {
67
                if (v != _visible) { // avoid changing the style
3✔
68
                    _visible = v;
2✔
69
                    this.content.style.display = v ? 'block' : 'none';
2✔
70
                    // TODO: add smooth transition for fade in/out
71
                }
72
            },
73
            get() {
74
                return _visible;
1✔
75
            },
76
        });
77

78
        this.isLabel = true;
9✔
79
        this.coordinates = coordinates;
9✔
80

81
        this.projectedPosition = { x: 0, y: 0 };
9✔
82
        this.boundaries = { left: 0, right: 0, top: 0, bottom: 0 };
9✔
83

84
        if (typeof content === 'string') {
9✔
85
            this.content = document.createElement('div');
8✔
86
            this.content.textContent = style.text.field;
8✔
87
        } else {
88
            this.content = content.cloneNode(true);
1✔
89
        }
90

91
        this.content.classList.add('itowns-label');
9✔
92
        this.content.style.userSelect = 'none';
9✔
93
        this.content.style.position = 'absolute';
9✔
94

95
        if (style.isStyle) {
9✔
96
            this.anchor = style.getTextAnchorPosition();
8✔
97
            this.styleOffset = style.text.offset;
8✔
98
            if (typeof content === 'string') {
8!
99
                if (style.text.haloWidth > 0) {
8!
100
                    this.content.classList.add('itowns-stroke-single');
×
101
                }
102
                style.applyToHTML(this.content)
8✔
103
                    .then((icon) => {
104
                        if (icon) { // Not sure if that test is needed...
8!
105
                            this.icon = icon;
×
106
                        }
107
                    });
108
            }
109
        } else {
110
            this.anchor = [0, 0];
1✔
111
            this.styleOffset = [0, 0];
1✔
112
        }
113

114
        this.iconOffset = { left: 0, right: 0, top: 0, bottom: 0 };
9✔
115

116
        this.zoom = {
9✔
117
            min: style.zoom && style.zoom.min != undefined ? style.zoom.min : 2,
26✔
118
            max: style.zoom && style.zoom.max != undefined ? style.zoom.max : 24,
26✔
119
        };
120

121
        this.order = style.order || 0;
9✔
122
        // Padding value, to avoid labels being too close to each other.
123
        this.padding = 2;
9✔
124
    }
125

126
    /**
1✔
127
     * Moves a label on the screen, using screen coordinates. It updates the
128
     * boundaries as it moves it.
129
     *
130
     * @param {number} x - X coordinates in pixels, from left.
131
     * @param {number} y - Y coordinates in pixels, from top.
132
     */
133
    updateProjectedPosition(x, y) {
134
        const X = Math.round(x);
4✔
135
        const Y = Math.round(y);
4✔
136
        if (X != this.projectedPosition.x || Y != this.projectedPosition.y) {
4!
137
            this.projectedPosition.x = X;
4✔
138
            this.projectedPosition.y = Y;
4✔
139

140
            this.boundaries.left = x + this.offset.left - this.padding;
4✔
141
            this.boundaries.right = x + this.offset.right + this.padding;
4✔
142
            this.boundaries.top = y + this.offset.top - this.padding;
4✔
143
            this.boundaries.bottom = y + this.offset.bottom + this.padding;
4✔
144

145
            // The boundaries of the label are the union of the boundaries of the text
146
            // and the boundaries of the icon, if it exists.
147
            // Checking if this.icon is not only zeros is mandatory, to prevent case
148
            // when a boundary is set to x or y coordinate
149
            if (
4!
150
                this.iconOffset.left !== 0 || this.iconOffset.right !== 0
16✔
151
                || this.iconOffset.top !== 0 || this.iconOffset.bottom !== 0
152
            ) {
153
                this.boundaries.left = Math.min(this.boundaries.left, x + this.iconOffset.left);
×
154
                this.boundaries.right = Math.max(this.boundaries.right, x + this.iconOffset.right);
×
155
                this.boundaries.top = Math.min(this.boundaries.top, y + this.iconOffset.top);
×
156
                this.boundaries.bottom = Math.max(this.boundaries.bottom, y + this.iconOffset.bottom);
×
157
            }
158
        }
159
    }
160

161
    updateCSSPosition() {
162
        // translate all content according to its given anchor
163
        this.content.style[STYLE_TRANSFORM] = `translate(${
2✔
164
            this.projectedPosition.x + this.offset.left
165
        }px, ${
166
            this.projectedPosition.y + this.offset.top
167
        }px)`;
168

169
        // translate possible icon inside content to cancel anchoring on it, so that it can later be positioned
170
        // according to its own anchor
171
        if (this.icon) {
2!
172
            this.icon.style[STYLE_TRANSFORM] = `translate(${-this.offset.left}px, ${-this.offset.top}px)`;
×
173
        }
174
    }
175

176
    /**
177
     * Updates the screen dimensions of the label, using
178
     * `getBoundingClientRect`.  It updates `width` and `height` property of the
179
     * label, and the boundaries.
180
     */
181
    initDimensions() {
182
        if (!this.offset) {
2!
183
            rect = this.content.getBoundingClientRect();
2✔
184
            const width = Math.round(rect.width);
2✔
185
            const height = Math.round(rect.height);
2✔
186
            this.offset = {
2✔
187
                left: width * this.anchor[0] + this.styleOffset[0],
188
                top: height * this.anchor[1] + this.styleOffset[1],
189
            };
190
            this.offset.right = this.offset.left + width;
2✔
191
            this.offset.bottom = this.offset.top + height;
2✔
192

193
            if (this.icon) {
2!
194
                rect = this.icon.getBoundingClientRect();
×
195
                this.iconOffset = {
×
196
                    left: Math.floor(rect.x),
197
                    top: Math.floor(rect.y),
198
                    right: Math.ceil(rect.x + rect.width),
199
                    bottom: Math.ceil(rect.y + rect.height),
200
                };
201
            }
202
        }
203
    }
204

205
    update3dPosition(crs) {
206
        this.coordinates.as(crs, coord).toVector3(this.position);
2✔
207
        this.updateMatrixWorld();
2✔
208
    }
209

210
    updateElevationFromLayer(layer, nodes) {
UNCOV
211
        if (layer.attachedLayers.filter(l => l.isElevationLayer).length == 0) {
×
UNCOV
212
            return;
×
213
        }
214

215
        let elevation = Math.max(0, DEMUtils.getElevationValueAt(layer, this.coordinates, DEMUtils.FAST_READ_Z, nodes));
×
216

217
        if (isNaN(elevation)) {
×
218
            elevation = Math.max(0, DEMUtils.getElevationValueAt(layer, this.coordinates, DEMUtils.FAST_READ_Z));
×
219
        }
220

221
        if (!isNaN(elevation) && elevation != this.coordinates.z) {
×
222
            this.coordinates.z = elevation;
×
223
        }
224
    }
225

226
    updateHorizonCullingPoint() {
227
        if (this.horizonCullingPoint) {
2!
228
            this.getWorldPosition(this.horizonCullingPoint);
2✔
229
        }
230
    }
1✔
231
}
232

233
export default Label;
1✔
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

© 2025 Coveralls, Inc