Skip to content

Commit 0797e75

Browse files
committed
draft 1
draft 2 draft 3 draft 4 draft 5 draft 6 DBC22-6776: fixed broken test cases removed commented out code DBC22-6776: switch to fixed pixels to cluster icons DBC22-6776: removed variables not used DBC22-6776: updated grouped icons styling DBC22-6776: updated grouped icon text color
1 parent 6ff76db commit 0797e75

8 files changed

Lines changed: 154 additions & 20 deletions

File tree

src/frontend/src/Components/data/featureStyleDefinitions.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import cameraIconHover from '../../images/mapIcons/camera-hover.png';
77
import cameraIconStatic from '../../images/mapIcons/camera-static.png';
88
import cameraIconHoverUnread from '../../images/mapIcons/camera-hover-unread.png';
99
import cameraIconStaticUnread from '../../images/mapIcons/camera-static-unread.png';
10+
import cameraGroupedIconActive from '../../images/mapIcons/camera-grouped-active.png';
11+
import cameraGroupedIconHover from '../../images/mapIcons/camera-grouped-hover.png';
12+
import cameraGroupedIconStatic from '../../images/mapIcons/camera-grouped-static.png';
1013

1114
// Ferries
1215
import ferryIconActive from '../../images/mapIcons/ferry-active.png';
@@ -209,6 +212,28 @@ export const cameraStyles = {
209212
}),
210213
};
211214

215+
// Camera icon styles
216+
export const cameraGroupedStyles = {
217+
static: new Style({
218+
image: new Icon({
219+
scale: 0.25,
220+
src: cameraGroupedIconStatic,
221+
}),
222+
}),
223+
hover: new Style({
224+
image: new Icon({
225+
scale: 0.25,
226+
src: cameraGroupedIconHover,
227+
}),
228+
}),
229+
active: new Style({
230+
image: new Icon({
231+
scale: 0.25,
232+
src: cameraGroupedIconActive,
233+
}),
234+
}),
235+
};
236+
212237
export const unreadCameraStyles = {
213238
static: new Style({
214239
image: new Icon({

src/frontend/src/Components/map/handlers/click.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ const camClickHandler = (
257257
feature.setCameraStyle('active');
258258
feature.set('clicked', true, true);
259259
feature.set('unread', false);
260+
feature.set('hovered', false);
260261

261262
if (isCamDetail) {
262263
// Do not auto center on feature on cam details page
@@ -580,8 +581,14 @@ export const pointerClickHandler = (
580581
mapContext
581582
) => {
582583
if (features.length) {
583-
const clickedFeature = features[0];
584-
switch (clickedFeature.getProperties()['type']) {
584+
let clickedFeature = features[0];
585+
586+
if (!clickedFeature.get('type')) {
587+
clickedFeature = features[0].values_?.features[0];
588+
}
589+
590+
591+
switch (clickedFeature?.getProperties()['type']) {
585592
case 'camera':
586593
trackEvent(
587594
'click',

src/frontend/src/Components/map/handlers/hover.js

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,22 @@ export const resetHoveredStates = (targetFeature, hoveredFeatureRef) => {
3030

3131
// Reset feature if target isn't clicked
3232
if (hoveredFeature && targetFeature != hoveredFeature) {
33+
34+
const clusterFeatures = hoveredFeature.get('features');
35+
if (clusterFeatures && clusterFeatures.length > 1) {
36+
clusterFeatures.forEach((f) => {
37+
if (!f.get('clicked')) {
38+
f.set('hovered', false);
39+
}
40+
});
41+
} else
42+
43+
44+
3345
if (!hoveredFeature.getProperties().clicked) {
3446
switch (hoveredFeature.getProperties()['type']) {
3547
case 'camera':
48+
hoveredFeature.set('hovered', false);
3649
hoveredFeature.setCameraStyle('static');
3750
break;
3851
case 'event': {
@@ -148,15 +161,34 @@ export const pointerMoveHandler = (e, mapRef, hoveredFeature) => {
148161
});
149162

150163
if (features.length) {
151-
const targetFeature = features[0];
152-
resetHoveredStates(targetFeature, hoveredFeature);
153-
hoveredFeature.current = targetFeature;
164+
let targetFeature = features[0];
165+
let clusterFeatures = targetFeature?.get('features');
166+
if(targetFeature && !targetFeature.get('type')) {
154167

155-
// Set hover style if feature isn't clicked
156-
switch (targetFeature.getProperties()['type']) {
168+
if (clusterFeatures && clusterFeatures.length === 1) {
169+
targetFeature = clusterFeatures[0];
170+
clusterFeatures = null;
171+
}
172+
}
173+
174+
const realFeature = targetFeature;
175+
resetHoveredStates(realFeature, hoveredFeature);
176+
hoveredFeature.current = realFeature;
177+
178+
if (clusterFeatures && clusterFeatures.length > 1) {
179+
clusterFeatures.forEach((f) => {
180+
if (!f.get('clicked')) {
181+
f.set('hovered', true);
182+
}
183+
});
184+
return;
185+
}
186+
187+
switch (realFeature?.get('type')) {
157188
case 'camera':
158-
if (!targetFeature.get('clicked')) {
159-
targetFeature.setCameraStyle('hover');
189+
if (!realFeature.get('clicked')) {
190+
realFeature.setCameraStyle('hover');
191+
realFeature.set('hovered', true);
160192
}
161193
return;
162194
case 'event':

src/frontend/src/Components/map/layers/camerasLayer.js

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
// OpenLayers
22
import { Point } from 'ol/geom';
3-
import { Style } from 'ol/style';
3+
import {Style, Fill, Text, Icon} from 'ol/style';
44
import * as ol from 'ol';
55
import VectorLayer from 'ol/layer/Vector';
66
import VectorSource from 'ol/source/Vector';
7+
import Cluster from 'ol/source/Cluster.js';
8+
// import CircleStyle from 'ol/style/Circle.js';
79

810
// Styling
9-
import { cameraStyles, unreadCameraStyles } from '../../data/featureStyleDefinitions.js';
11+
import { cameraStyles, cameraGroupedStyles, unreadCameraStyles } from '../../data/featureStyleDefinitions.js';
1012

11-
export function getCamerasLayer(cameras, projectionCode, mapContext, referenceData, updateReferenceFeature, setLoadingLayers) {
13+
export function getCamerasLayer(cameras, projectionCode, mapContext, referenceData, updateReferenceFeature, setLoadingLayers, pixelDistance) {
1214
const vectorSource = new VectorSource();
1315

1416
cameras.forEach(camera => {
@@ -47,12 +49,68 @@ export function getCamerasLayer(cameras, projectionCode, mapContext, referenceDa
4749
}
4850
});
4951

50-
return new VectorLayer({
52+
const clusterSource = new Cluster({
53+
distance: pixelDistance,
54+
source: vectorSource,
55+
});
56+
57+
const styleCache = {};
58+
59+
const layer = new VectorLayer({
5160
classname: 'webcams',
5261
visible: mapContext.visible_layers.highwayCams,
53-
source: vectorSource,
54-
style: () => null
62+
source: clusterSource,
63+
64+
style: function (feature) {
65+
const features = feature.get('features');
66+
const size = features.length;
67+
68+
// single camera
69+
if (size === 1) {
70+
const cam = features[0];
71+
if (cam.get('clicked')) return cameraStyles.active;
72+
if (cam.get('hovered')) return cameraStyles.hover;
73+
return cameraStyles.static;
74+
}
75+
76+
const cam = features[0];
77+
let state = 'static';
78+
if (cam.get('clicked')) {
79+
state = 'active';
80+
} else if (cam.get('hovered')) {
81+
state = 'hover';
82+
}
83+
84+
const cacheKey = `${state}_${size}`;
85+
let style = styleCache[cacheKey];
86+
87+
if (!style) {
88+
const textColor = state === 'static' ? '#255A90' : '#fff';
89+
90+
style = new Style({
91+
image: new Icon({
92+
scale: 0.25,
93+
src: cameraGroupedStyles[state].getImage().getSrc(),
94+
}),
95+
text: new Text({
96+
text: String(size),
97+
font: 'bold 12px sans-serif',
98+
fill: new Fill({ color: textColor }),
99+
offsetX: 2,
100+
offsetY: 1,
101+
textAlign: 'left',
102+
}),
103+
});
104+
styleCache[cacheKey] = style;
105+
}
106+
107+
return style;
108+
},
55109
});
110+
111+
layer.getClusterSource = () => clusterSource;
112+
113+
return layer;
56114
}
57115

58116
export function updateCamerasLayer(cameras, layer, setLoadingLayers) {
@@ -62,7 +120,11 @@ export function updateCamerasLayer(cameras, layer, setLoadingLayers) {
62120
}, {});
63121

64122
for (const feature of layer.getSource().getFeatures()) {
65-
const camera = camerasLookup[feature.getId()];
123+
let camera = camerasLookup[feature.getId()];
124+
if (!camera) {
125+
camera = camerasLookup[feature.values_.features[0].getId()];
126+
}
127+
66128

67129
if (!camera) { // camera no longer in list from API
68130
feature.setStyle(new Style(null));
@@ -76,12 +138,16 @@ export function updateCamerasLayer(cameras, layer, setLoadingLayers) {
76138
feature.setProperties(camera); // update feature with latest API data.
77139

78140
if (feature.get('clicked')) { continue; }
79-
80-
feature.setCameraStyle('static');
141+
try {
142+
feature.setCameraStyle('static');
143+
}
144+
catch {
145+
feature.values_.features[0].setCameraStyle('static');
146+
}
81147
}
82148

83149
setLoadingLayers(prevState => ({
84150
...prevState,
85151
cameras: false
86152
}));
87-
}
153+
}

src/frontend/src/Components/map/layers/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const loadLayer = (mapLayers, mapRef, mapContext, key, dataList, filtered
4747
mapRef.current.removeLayer(mapLayers.current[key]);
4848
}
4949

50+
5051
if (dataList) {
5152
if (!mapLayers.current[key] || key == 'routeLayer') {
5253
// Generate and add layer if it doesn't exist
@@ -56,7 +57,8 @@ export const loadLayer = (mapLayers, mapRef, mapContext, key, dataList, filtered
5657
mapContext,
5758
referenceData,
5859
updateReferenceFeature,
59-
setLoadingLayers
60+
setLoadingLayers,
61+
50
6062
);
6163

6264
mapRef.current.addLayer(mapLayers.current[key]);
@@ -70,6 +72,8 @@ export const loadLayer = (mapLayers, mapRef, mapContext, key, dataList, filtered
7072
}
7173
}
7274

75+
76+
7377
export const enableReferencedLayer = (referenceData, mapContext) => {
7478
// Do nothing if no reference data
7579
if (!referenceData) return;
3.27 KB
Loading
3.36 KB
Loading
3.16 KB
Loading

0 commit comments

Comments
 (0)