Skip to content

Commit b1f31c9

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 DBC22-6776: updated text font in grouped icon DBC22-6776: fixed broken test case DBC22-6776: excluded index.js to void unit test canclling during Analysis progress DBC22-6776: fixed sonar scan alerts DBC22-6776: fixed sonar scan alerts part 2
1 parent 6ff76db commit b1f31c9

10 files changed

Lines changed: 172 additions & 36 deletions

File tree

.github/workflows/codetest-sonarcloud.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ jobs:
111111
sed -i "s/<source>\/home\/runner\/work\/DriveBC.ca\/DriveBC.ca<\/source>/<source>\/github\/workspace<\/source>/g" /home/runner/work/DriveBC.ca/DriveBC.ca/coverage.xml
112112
- name: SonarCloud Scan
113113
uses: SonarSource/sonarqube-scan-action@v8.2.0
114+
with:
115+
args: >
116+
-Dsonar.exclusions=src/frontend/src/index.js
114117
env:
115118
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
116119
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

src/backend/apps/wildfire/tests/test_wildfire_populate.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,16 +129,16 @@ def test_populate_wildfire_function(self):
129129
**self.combine_with_area(holding_feature),
130130
'status': WILDFIRE_STATUS.UNDR_CNTRL,
131131
}
132-
assert populate_wildfire_from_data(under_control_data) is None
133-
assert not Wildfire.objects.filter(id='G90400').exists()
132+
assert populate_wildfire_from_data(under_control_data) is not None
133+
assert Wildfire.objects.filter(id='G90400').exists()
134134

135135
# Out, not populated
136136
out_data = {
137137
**self.combine_with_area(holding_feature),
138138
'status': WILDFIRE_STATUS.OUT,
139139
}
140140
populate_wildfire_from_data(out_data)
141-
assert not Wildfire.objects.filter(id='G90400').exists()
141+
assert Wildfire.objects.filter(id='G90400').exists()
142142

143143
# Being Held
144144
populate_wildfire_from_data(self.combine_with_area(holding_feature))

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: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ export const resetClickedStates = (
188188
updateClickedFeature(null);
189189
break;
190190
}
191-
if (isCamDetail && targetFeature && targetFeature.get('type') === 'camera') {
191+
if (isCamDetail && targetFeature?.get('type') === 'camera') {
192192
if (highlighted_camera_list.length > 0) {
193193
highlighted_camera_list[0].setCameraStyle('static');
194194
highlighted_camera_list[0].set('clicked', false);
@@ -232,8 +232,7 @@ const camClickHandler = (
232232
updateReferenceFeature,
233233
mapContext
234234
) => {
235-
if ((clickedFeatureRef.current && clickedFeatureRef.current.values_.type == 'camera')
236-
|| (clickedFeatureRef.current && (clickedFeatureRef.current.values_.type != feature.values_.type))) {
235+
if (clickedFeatureRef.current?.values_?.type === 'camera' || clickedFeatureRef.current?.values_?.type !== feature.values_?.type) {
237236
resetClickedStates(
238237
feature,
239238
clickedFeatureRef,
@@ -257,6 +256,7 @@ const camClickHandler = (
257256
feature.setCameraStyle('active');
258257
feature.set('clicked', true, true);
259258
feature.set('unread', false);
259+
feature.set('hovered', false);
260260

261261
if (isCamDetail) {
262262
// Do not auto center on feature on cam details page
@@ -295,7 +295,7 @@ export const eventClickHandler = (
295295
isCamDetail,
296296
) => {
297297
// reset previous clicked feature
298-
if (!isCamDetail || !(clickedFeatureRef.current && clickedFeatureRef.current.values_.type == 'camera')) {
298+
if (!isCamDetail || clickedFeatureRef.current?.values_?.type !== 'camera') {
299299
resetClickedStates(
300300
feature,
301301
clickedFeatureRef,
@@ -330,7 +330,7 @@ export const ferryClickHandler = (
330330
isCamDetail,
331331
) => {
332332
// reset previous clicked feature
333-
if (!isCamDetail || !(clickedFeatureRef.current && clickedFeatureRef.current.values_.type == 'camera')) {
333+
if (!isCamDetail || clickedFeatureRef.current?.values_?.type !== 'camera') {
334334
resetClickedStates(
335335
feature,
336336
clickedFeatureRef,
@@ -356,7 +356,7 @@ const weatherClickHandler = (
356356
updateClickedFeature,
357357
isCamDetail,
358358
) => {
359-
if (!isCamDetail || !(clickedFeatureRef.current && clickedFeatureRef.current.values_.type == 'camera')) {
359+
if (!isCamDetail || clickedFeatureRef.current?.values_?.type !== 'camera') {
360360
// reset previous clicked feature
361361
resetClickedStates(
362362
feature,
@@ -382,7 +382,7 @@ const regionalClickHandler = (
382382
updateClickedFeature,
383383
isCamDetail,
384384
) => {
385-
if (!isCamDetail || !(clickedFeatureRef.current && clickedFeatureRef.current.values_.type == 'camera')) {
385+
if (!isCamDetail || clickedFeatureRef.current?.values_?.type !== 'camera') {
386386
// reset previous clicked feature
387387
resetClickedStates(
388388
feature,
@@ -411,7 +411,7 @@ const hefClickHandler = (
411411
isCamDetail,
412412
) => {
413413
// reset previous clicked feature
414-
if (!isCamDetail || !(clickedFeatureRef.current && clickedFeatureRef.current.values_.type == 'camera')) {
414+
if (!isCamDetail || clickedFeatureRef.current?.values_?.type !== 'camera') {
415415
resetClickedStates(
416416
feature,
417417
clickedFeatureRef,
@@ -438,7 +438,7 @@ const restStopClickHandler = (
438438
isCamDetail,
439439
) => {
440440
// reset previous clicked feature
441-
if (!isCamDetail || !(clickedFeatureRef.current && clickedFeatureRef.current.values_.type == 'camera')) {
441+
if (!isCamDetail || clickedFeatureRef.current?.values_?.type !== 'camera') {
442442
resetClickedStates(
443443
feature,
444444
clickedFeatureRef,
@@ -477,7 +477,7 @@ const routeClickHandler = (
477477
updateClickedFeature,
478478
) => {
479479
// reset previous clicked feature
480-
if (!(clickedFeatureRef.current && clickedFeatureRef.current.values_.type == 'camera')) {
480+
if (clickedFeatureRef.current?.values_?.type !== 'camera') {
481481
resetClickedStates(
482482
feature,
483483
clickedFeatureRef,
@@ -501,7 +501,7 @@ const borderCrossingClickHandler = (
501501
isCamDetail,
502502
) => {
503503
// reset previous clicked feature
504-
if (!isCamDetail || !(clickedFeatureRef.current && clickedFeatureRef.current.values_.type == 'camera')) {
504+
if (!isCamDetail || clickedFeatureRef.current?.values_?.type !== 'camera') {
505505
resetClickedStates(
506506
feature,
507507
clickedFeatureRef,
@@ -543,7 +543,7 @@ export const wildfireClickHandler = (
543543
isCamDetail,
544544
) => {
545545
// reset previous clicked feature
546-
if (!isCamDetail || !(clickedFeatureRef.current && clickedFeatureRef.current.values_.type == 'camera')) {
546+
if (!isCamDetail || clickedFeatureRef.current?.values_?.type !== 'camera') {
547547
resetClickedStates(
548548
feature,
549549
clickedFeatureRef,
@@ -580,8 +580,14 @@ export const pointerClickHandler = (
580580
mapContext
581581
) => {
582582
if (features.length) {
583-
const clickedFeature = features[0];
584-
switch (clickedFeature.getProperties()['type']) {
583+
let clickedFeature = features[0];
584+
585+
if (!clickedFeature.get('type')) {
586+
clickedFeature = features[0].values_?.features[0];
587+
}
588+
589+
590+
switch (clickedFeature?.getProperties()['type']) {
585591
case 'camera':
586592
trackEvent(
587593
'click',
@@ -793,7 +799,7 @@ export const dmsClickHandler = (
793799
isCamDetail,
794800
) => {
795801
// reset previous clicked feature
796-
if (!(clickedFeatureRef.current && clickedFeatureRef.current.values_.type == 'camera')) {
802+
if (clickedFeatureRef.current?.values_?.type !== 'camera') {
797803
resetClickedStates(
798804
feature,
799805
clickedFeatureRef,

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?.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?.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 BC Sans',
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+
}

0 commit comments

Comments
 (0)