Skip to content

Commit c09c824

Browse files
ibesoragithub-actions[bot]
authored andcommitted
Fix active appearance optimization (internal-7846)
GitOrigin-RevId: 0c34fd018a1ef4d2689164d6b26da192d41238c2
1 parent 484dd82 commit c09c824

File tree

6 files changed

+275
-104
lines changed

6 files changed

+275
-104
lines changed

debug/appearances.html

Lines changed: 271 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -21,115 +21,283 @@
2121
mapboxgl.accessToken = getAccessToken();
2222

2323
const map = new mapboxgl.Map({
24-
container: 'map',
25-
// Choose from Mapbox's core styles, or make your own style with Mapbox Studio
26-
style: 'mapbox://styles/mapbox/standard',
27-
zoom: 15,
28-
center: [-71.97722138410576, -13.517379300798098]
29-
});
30-
31-
// Wait until the map has finished loading.
32-
map.on('load', () => {
33-
map.setConfigProperty('basemap', 'lightPreset', 'dusk');
34-
map.loadImage(
35-
'https://docs.mapbox.com/mapbox-gl-js/assets/cat.png',
36-
(error, image) => {
24+
container: 'map',
25+
// Choose from Mapbox's core styles, or make your own style with Mapbox Studio
26+
style: 'mapbox://styles/mapbox/standard',
27+
// Disable the POI labels to only show the ones we will import
28+
config: {
29+
basemap: {
30+
showPointOfInterestLabels: false
31+
}
32+
},
33+
zoom: 15.5,
34+
center: [1.8447281852, 42.10025506]
35+
});
36+
37+
// Wait until the map has finished loading.
38+
map.on('load', () => {
39+
// Load an image for every feature state.
40+
// When all images are loaded, add the layer to the map
41+
const numIconsToLoad = 4;
42+
let numIconsLoaded = 0;
43+
map.loadImage('hotel.png', (error, image) => {
3744
if (error) throw error;
3845

39-
// Add the image to the map style.
40-
map.addImage('cat', image);
41-
// Add a custom vector tileset source. This tileset contains
42-
// point features.
43-
map.addSource('museums', {
44-
type: 'vector',
45-
url: 'mapbox://mapbox.2opop9hr'
46-
});
47-
// Add a layer to show a circle on each point
48-
map.addLayer({
49-
'id': 'circles',
50-
'type': 'circle',
51-
'source': 'museums',
52-
'paint': {
53-
'circle-color': 'red'
54-
},
55-
'layout': {
56-
'visibility': 'visible',
46+
map.addImage('hotel', image);
47+
numIconsLoaded++;
48+
if (numIconsLoaded === numIconsToLoad) addLayer();
49+
});
50+
51+
map.loadImage(
52+
'hotel-active.png',
53+
(error, image) => {
54+
if (error) throw error;
55+
56+
map.addImage('hotel-active', image);
57+
numIconsLoaded++;
58+
if (numIconsLoaded === numIconsToLoad) addLayer();
59+
}
60+
);
61+
62+
map.loadImage(
63+
'hotel-clicked.png',
64+
(error, image) => {
65+
if (error) throw error;
66+
67+
map.addImage('hotel-clicked', image);
68+
numIconsLoaded++;
69+
if (numIconsLoaded === numIconsToLoad) addLayer();
70+
}
71+
);
72+
73+
map.loadImage(
74+
'hotel-hover.png',
75+
(error, image) => {
76+
if (error) throw error;
77+
78+
map.addImage('hotel-hover', image);
79+
numIconsLoaded++;
80+
if (numIconsLoaded === numIconsToLoad) addLayer();
81+
}
82+
);
83+
84+
map.addSource('points', {
85+
type: 'geojson',
86+
data: getGeoJSONData()
87+
});
88+
});
89+
90+
function addLayer() {
91+
// Add a layer to show an icon on every point.
92+
map.addLayer({
93+
'id': 'points',
94+
'type': 'symbol',
95+
'source': 'points',
96+
'layout': {
97+
'icon-allow-overlap': true,
98+
// Define an icon and size for the default appearance of symbols in this layer
99+
'icon-image': 'hotel',
100+
'icon-size': 0.75
101+
},
102+
'appearances': [
103+
{
104+
'name': 'clicked',
105+
'condition': [
106+
'boolean',
107+
['feature-state', 'currentlySelected'],
108+
false
109+
],
110+
'properties': {
111+
// This icon will be used when the currentlySelected feature state is active
112+
'icon-image': 'hotel-active'
113+
}
57114
},
58-
'source-layer': 'museum-cusco'
59-
});
60-
// Add a layer to show icons on top of the points.
61-
// Icons will grow when hovered and when clicked.
62-
map.addLayer({
63-
'id': 'museums',
64-
'type': 'symbol',
65-
'source': 'museums',
66-
'layout': {
67-
'visibility': 'visible',
68-
'icon-image': "cat",
69-
'icon-size': 0.2,
115+
{
116+
'name': 'hovered',
117+
'condition': ['boolean', ['feature-state', 'hover'], false],
118+
'properties': {
119+
// This icon will be used when the hover feature state is active and currentlySelected is not
120+
'icon-image': 'hotel-hover'
121+
}
70122
},
71-
'appearances': [
72-
{
73-
'name': 'clicked',
74-
'condition': ['==', ['boolean', ['feature-state', 'clicked'], false], true],
75-
'properties': {
76-
'icon-size': 0.5,
77-
}
78-
},
79-
{
80-
'name': 'hovered',
81-
'condition': ['==', ['boolean', ['feature-state', 'hover'], false], true],
82-
'properties': {
83-
'icon-size': 0.3,
84-
}
85-
},
86-
],
87-
'source-layer': 'museum-cusco'
88-
});
89-
}
90-
);
91-
});
92-
93-
let selectedFeature = null;
94-
map.addInteraction('hover-in', {
95-
type: 'mouseenter',
96-
target: { layerId: 'museums' },
97-
handler: (e) => {
98-
map.setFeatureState(e.feature, {hover: true});
99-
map.getCanvas().style.cursor = 'pointer';
100-
}
101-
});
102-
103-
map.addInteraction('hover-out', {
104-
type: 'mouseleave',
105-
target: { layerId: 'museums' },
106-
handler: (e) => {
107-
map.setFeatureState(e.feature, {hover: false});
108-
map.getCanvas().style.cursor = '';
109-
}
110-
});
111-
112-
map.addInteraction('click', {
113-
type: 'click',
114-
target: { layerId: 'museums' },
115-
handler: (e) => {
116-
if (selectedFeature) {
117-
map.setFeatureState(selectedFeature, {clicked: false});
118-
}
119-
selectedFeature = e.feature;
120-
map.setFeatureState(e.feature, {clicked: true});
123+
{
124+
'name': 'has-been-clicked',
125+
'condition': [
126+
'boolean',
127+
['feature-state', 'hasBeenClicked'],
128+
false
129+
],
130+
'properties': {
131+
// This icon will be used then the hasBeenClicked feature state is active and neither hover nor currentlySelected are
132+
'icon-image': 'hotel-clicked'
133+
}
134+
}
135+
]
136+
});
137+
138+
// Store the currently selected feature and the features that have been
139+
// selected some time
140+
let selectedFeature = null;
141+
const clickedFeatures = [];
142+
143+
// When the mouse hovers over the feature we set the hover feature state to true
144+
// and change the pointer
145+
map.addInteraction('hover-in', {
146+
type: 'mouseenter',
147+
target: { layerId: 'points' },
148+
handler: (e) => {
149+
map.setFeatureState(e.feature, { hover: true });
150+
map.getCanvas().style.cursor = 'pointer';
151+
}
152+
});
153+
154+
// When the mouse hovers outside the feature we set the hover feature state to false
155+
// and change the pointer
156+
map.addInteraction('hover-out', {
157+
type: 'mouseleave',
158+
target: { layerId: 'points' },
159+
handler: (e) => {
160+
map.setFeatureState(e.feature, { hover: false });
161+
map.getCanvas().style.cursor = '';
162+
}
163+
});
164+
165+
// When the mouse is clicked on a feature we set the currentlySelected feature state to true,
166+
// unselect the previous select one if any and store this feature both as the selected feature
167+
// and in the list of features that have been selected
168+
map.addInteraction('click', {
169+
type: 'click',
170+
target: { layerId: 'points' },
171+
handler: (e) => {
172+
// If we click on a feature and there was some of it selected, we first
173+
// deselect that one
174+
if (selectedFeature) {
175+
map.setFeatureState(selectedFeature, {
176+
currentlySelected: false
177+
});
178+
}
179+
// We store this feature as the currently selected feature and in the list
180+
// of feature that have been selected
181+
selectedFeature = e.feature;
182+
clickedFeatures.push(e.feature);
183+
map.setFeatureState(e.feature, {
184+
currentlySelected: true,
185+
hasBeenClicked: true
186+
});
187+
}
188+
});
189+
190+
// When the mouse is clicked outside of any feature, we unselect the currently selected feature if
191+
// there's any or remove all features from the list of feature that have been selected to get back
192+
// to the initial state
193+
map.addInteraction('map-click', {
194+
type: 'click',
195+
handler: () => {
196+
// When we click on the map we unselect the currently selected feature
197+
// If there was none selected we reset the state of all features to the default one
198+
if (selectedFeature) {
199+
map.setFeatureState(selectedFeature, {
200+
currentlySelected: false
201+
});
202+
selectedFeature = null;
203+
} else {
204+
clickedFeatures.forEach((f) => {
205+
map.setFeatureState(f, { hasBeenClicked: false });
206+
});
207+
clickedFeatures.length = 0;
208+
}
209+
}
210+
});
121211
}
122-
});
123-
124-
map.addInteraction('map-click', {
125-
type: 'click',
126-
handler: () => {
127-
if (selectedFeature) {
128-
map.setFeatureState(selectedFeature, { clicked: false });
129-
selectedFeature = null;
130-
}
212+
213+
function getGeoJSONData() {
214+
return {
215+
'type': 'FeatureCollection',
216+
'features': [
217+
{
218+
'type': 'Feature',
219+
'id': 1,
220+
'properties': {},
221+
'geometry': {
222+
'coordinates': [1.8452993238082342, 42.100164223399275],
223+
'type': 'Point'
224+
}
225+
},
226+
{
227+
'type': 'Feature',
228+
'id': 2,
229+
'properties': {},
230+
'geometry': {
231+
'coordinates': [1.8438590191857145, 42.1004178052402],
232+
'type': 'Point'
233+
}
234+
},
235+
{
236+
'type': 'Feature',
237+
'id': 3,
238+
'properties': {},
239+
'geometry': {
240+
'coordinates': [1.844225198327564, 42.10130533369667],
241+
'type': 'Point'
242+
}
243+
},
244+
{
245+
'type': 'Feature',
246+
'id': 4,
247+
'properties': {},
248+
'geometry': {
249+
'coordinates': [1.8443594640122, 42.0990955459275],
250+
'type': 'Point'
251+
}
252+
},
253+
{
254+
'type': 'Feature',
255+
'id': 5,
256+
'properties': {},
257+
'geometry': {
258+
'coordinates': [1.8449697625811154, 42.09869705141318],
259+
'type': 'Point'
260+
}
261+
},
262+
{
263+
'type': 'Feature',
264+
'id': 6,
265+
'properties': {},
266+
'geometry': {
267+
'coordinates': [1.8471058075726603, 42.09978384873651],
268+
'type': 'Point'
269+
}
270+
},
271+
{
272+
'type': 'Feature',
273+
'id': 7,
274+
'properties': {},
275+
'geometry': {
276+
'coordinates': [1.8455739474818813, 42.10182152060625],
277+
'type': 'Point'
278+
}
279+
},
280+
{
281+
'type': 'Feature',
282+
'id': 8,
283+
'properties': {},
284+
'geometry': {
285+
'coordinates': [1.8427787800360136, 42.10039061289771],
286+
'type': 'Point'
287+
}
288+
},
289+
{
290+
'type': 'Feature',
291+
'id': 9,
292+
'properties': {},
293+
'geometry': {
294+
'coordinates': [1.8433280487479635, 42.0994396753579],
295+
'type': 'Point'
296+
}
297+
}
298+
]
299+
};
131300
}
132-
});
133301

134302
</script>
135303
</body>

debug/hotel-active.png

2.83 KB
Loading

debug/hotel-clicked.png

1.95 KB
Loading

debug/hotel-hover.png

1.86 KB
Loading

debug/hotel.png

1.45 KB
Loading

src/data/bucket/symbol_bucket.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1002,7 +1002,10 @@ class SymbolBucket implements Bucket {
10021002
};
10031003

10041004
const activeAppearance = this.layers[0].appearances && this.layers[0].appearances.find(a => a.isActive({globals: globalProperties, feature: evaluationFeature, canonical, featureState: featureStateForThis}));
1005-
if (featureData.activeAppearance === activeAppearance) continue;
1005+
if (featureData.activeAppearance === activeAppearance) {
1006+
vertexOffset += symbolInstance.numIconVertices;
1007+
continue;
1008+
}
10061009

10071010
if (activeAppearance) {
10081011
featureData.activeAppearance = activeAppearance;

0 commit comments

Comments
 (0)