Skip to content

Commit 8f1cf60

Browse files
authored
Merge pull request #1394 from PADAS/ERA-12142
[ERA-12142] Vector tile caching
2 parents efe184c + 852c0fc commit 8f1cf60

File tree

9 files changed

+172
-52
lines changed

9 files changed

+172
-52
lines changed

.github/workflows/develop-workflow.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
run: ci-helpers/makeversion.py
5050

5151
- name: Install dependencies
52-
run: yarn --ignore-scripts
52+
run: yarn install
5353

5454
- name: Run tests
5555
run: yarn test-ci

react-app-config/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ module.exports = function (webpackEnv) {
670670
// Bump up the default maximum size (2mb) that's precached,
671671
// to make lazy-loading failure scenarios less likely.
672672
// See https://github.com/cra-template/pwa/issues/13#issuecomment-722667270
673-
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
673+
maximumFileSizeToCacheInBytes: 10 * 1024 * 1024,
674674
}),
675675
// TypeScript type checking
676676
useTypeScript &&

src/App.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,26 @@ export const App = () => {
109109
finishDrag(e);
110110
}, [disallowDragAndDrop, finishDrag]);
111111

112+
113+
// set user scope for service worker caching
114+
useEffect(() => {
115+
if (navigator?.serviceWorker?.controller) {
116+
if (user?.id) {
117+
const scopeHash = selectedUserProfile?.id ?? user.id;
118+
navigator.serviceWorker.controller.postMessage({
119+
type: 'SET_SCOPE',
120+
scope: { hash: scopeHash }
121+
});
122+
} else {
123+
// Clear scope when user logs out
124+
navigator.serviceWorker.controller.postMessage({
125+
type: 'SET_SCOPE',
126+
scope: { hash: null }
127+
});
128+
}
129+
}
130+
}, [user, selectedUserProfile]);
131+
112132
useEffect(() => {
113133
/* use these catch blocks to provide error toasts if/as desired */
114134
dispatch(fetchEventTypes());

src/Map/index.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -589,9 +589,27 @@ const Map = ({ children, onMapLoad, socket }) => {
589589
// querying from the root /static/ dir of the host means this is one of our static assets, let's get it
590590
// if the map says it's missing.
591591
if (id.includes('/static/')) {
592-
const src = id.replace(/(\.svg|\.png|\.jpg).*$/, '$1');
592+
const dimensions = {};
593+
const match = id.match(/^(.*?)(?:-([^-.]+)-([^-.]+))?$/);
594+
595+
let src = id;
596+
if (match) {
597+
const [, path, width, height] = match;
598+
src = path;
599+
600+
if (width && width !== 'x') {
601+
dimensions.width = Number(width);
602+
}
603+
if (height && height !== 'x') {
604+
dimensions.height = Number(height);
605+
}
606+
}
607+
608+
// Sanitize the src path to remove internal icon ID suffixes
609+
src = src.replace(/(\.svg|\.png|\.jpg).*$/, '$1');
610+
593611
try {
594-
const img = await addMapImage({ src, id });
612+
await addMapImage({ src, id, ...dimensions });
595613
} catch (error) {
596614
console.warn('Error adding map image:', { event, error });
597615
}

src/SpatialFeaturesLayer/index.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import React, { memo, useContext, useMemo, useCallback, useEffect } from 'react';
22
import { useSelector } from 'react-redux';
33
import { MapContext } from '../App';
4+
import { addMapImage } from '../utils/map';
45
import { API_URL, DEFAULT_SYMBOL_LAYOUT, DEFAULT_SYMBOL_PAINT } from '../constants';
56

7+
import MarkerImage from '../common/images/icons/mapbox-blue-marker-icon.png';
8+
import RangerStationsImage from '../common/images/icons/ranger-stations.png';
9+
610
const SPATIAL_FEATURES_SOURCE = 'spatial-features-source';
711

812
const VECTOR_TILE_URL = `${API_URL}spatialfeatures/tiles/{z}/{x}/{y}.pbf`;
@@ -14,6 +18,8 @@ export const LINES_LAYER_ID = 'spatial-features-lines';
1418
export const POLYGONS_LAYER_ID = 'spatial-features-polygons';
1519
// const POLYGONS_LABELS_LAYER_ID = 'spatial-features-polygon-labels';
1620

21+
const BEFORE_LAYER_ID = 'feature-separation-layer';
22+
1723
const DEFAULT_LINE_PAINT_COLOR = [
1824
'case',
1925
['has', 'stroke'], ['get', 'stroke'],
@@ -26,10 +32,11 @@ const DEFAULT_LINE_PAINT_COLOR = [
2632
const DEFAULT_POLYGON_FILL_COLOR = [
2733
'case',
2834
['has', 'fill'], ['get', 'fill'],
29-
['has', 'color'], ['get', 'color'],
35+
['has', 'fill-color'], ['get', 'fill-color'],
3036
['has', 'fill_color'], ['get', 'fill_color'],
37+
['has', 'color'], ['get', 'color'],
3138
['has', 'stroke'], ['get', 'stroke'],
32-
'#ff6600'
39+
'rgba(255, 102, 0, 0)'
3340
];
3441

3542

@@ -114,7 +121,7 @@ const SpatialFeaturesLayer = ({ onFeatureClick }) => {
114121
...DEFAULT_SYMBOL_PAINT
115122
},
116123
filter: symbolLayerFilter
117-
});
124+
}, BEFORE_LAYER_ID);
118125
}
119126

120127
if (!map.getLayer(LINES_LAYER_ID)) {
@@ -131,7 +138,7 @@ const SpatialFeaturesLayer = ({ onFeatureClick }) => {
131138
['has', 'width'], ['get', 'width'],
132139
['has', 'line_width'], ['get', 'line_width'],
133140
['has', 'stroke_width'], ['get', 'stroke_width'],
134-
3
141+
1,
135142
],
136143
'line-opacity': [
137144
'case',
@@ -143,7 +150,7 @@ const SpatialFeaturesLayer = ({ onFeatureClick }) => {
143150
]
144151
},
145152
filter: lineLayerFilter
146-
});
153+
}, BEFORE_LAYER_ID);
147154
}
148155

149156
if (!map.getLayer(POLYGONS_LAYER_ID)) {
@@ -166,11 +173,11 @@ const SpatialFeaturesLayer = ({ onFeatureClick }) => {
166173
['has', 'stroke'], ['get', 'stroke'],
167174
['has', 'outline_color'], ['get', 'outline_color'],
168175
['has', 'border_color'], ['get', 'border_color'],
169-
'#ff6600'
176+
'rgba(255, 102, 0, 0.25)'
170177
]
171178
},
172179
filter: polygonLayerFilter
173-
});
180+
}, BEFORE_LAYER_ID);
174181
}
175182

176183
/* // Add separate label layers for each geometry type
@@ -361,6 +368,15 @@ const SpatialFeaturesLayer = ({ onFeatureClick }) => {
361368
}
362369
}, [map, polygonLayerFilter]);
363370

371+
useEffect(() => {
372+
if (!map?.hasImage?.('marker-icon')) {
373+
addMapImage({ src: MarkerImage, id: 'marker-icon' });
374+
}
375+
if (!map?.hasImage?.('ranger-stations')) {
376+
addMapImage({ src: RangerStationsImage, id: 'ranger-stations' });
377+
}
378+
}, [map]);
379+
364380
return null;
365381
};
366382

src/SpatialFeaturesLayer/index.test.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ jest.mock('../App', () => {
1515
// Mock these to prevent dependency chain issues
1616
jest.mock('../utils/analyzers', () => ({}));
1717
jest.mock('../ducks/analyzers', () => ({}));
18+
jest.mock('../utils/map', () => ({
19+
addMapImage: jest.fn()
20+
}));
1821

1922
jest.mock('../constants', () => ({
2023
API_URL: 'http://test-api.com/',
@@ -39,7 +42,7 @@ jest.mock('../constants', () => ({
3942
ANALYZER_POLYS_CRITICAL_SOURCE: 'analyzer-polygon-critical-source',
4043
ANALYZER_POLYS_WARNING_SOURCE: 'analyzer-polygon-warning-source',
4144
CLUSTERS_SOURCE_ID: 'clusters-source'
42-
}
45+
},
4346
}));
4447

4548
jest.mock('react-redux', () => ({
@@ -102,7 +105,7 @@ describe('SpatialFeaturesLayer', () => {
102105
['==', ['geometry-type'], 'Point'],
103106
['!', ['in', ['get', 'id'], ['literal', ['hidden-feature-1', 'hidden-feature-2']]]]
104107
])
105-
}));
108+
}), 'feature-separation-layer');
106109

107110
// Verify line layer was added
108111
expect(mockMap.addLayer).toHaveBeenCalledWith(expect.objectContaining({
@@ -115,7 +118,7 @@ describe('SpatialFeaturesLayer', () => {
115118
['==', ['geometry-type'], 'LineString'],
116119
['!', ['in', ['get', 'id'], ['literal', ['hidden-feature-1', 'hidden-feature-2']]]]
117120
])
118-
}));
121+
}), 'feature-separation-layer');
119122

120123
// Verify polygon layer was added
121124
expect(mockMap.addLayer).toHaveBeenCalledWith(expect.objectContaining({
@@ -128,7 +131,7 @@ describe('SpatialFeaturesLayer', () => {
128131
['==', ['geometry-type'], 'Polygon'],
129132
['!', ['in', ['get', 'id'], ['literal', ['hidden-feature-1', 'hidden-feature-2']]]]
130133
])
131-
}));
134+
}), 'feature-separation-layer');
132135

133136
// Verify click handlers were added
134137
expect(mockMap.on).toHaveBeenCalledWith('click', SYMBOLS_LAYER_ID, expect.any(Function));

src/ducks/subjects.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,23 +81,21 @@ export const clearSubjectData = () => ({
8181
});
8282

8383
export const fetchSubjectGroups = () => dispatch => axios.get(SUBJECT_GROUPS_API_URL)
84-
.then(response => dispatch(fetchSubjectGroupsSuccess(response)));
84+
.then(response => dispatch(fetchSubjectGroupsSuccess(response)))
85+
.catch(_error => dispatch(fetchSubjectGroupsError())); // Fallback to empty array on error
8586

8687
const fetchMapSubjectsSuccess = response => ({
8788
type: FETCH_MAP_SUBJECTS_SUCCESS,
8889
payload: response.data,
8990
});
90-
/*
91-
const fetchMapSubjectsError = error => ({
92-
type: FETCH_MAP_SUBJECTS_ERROR,
93-
payload: error,
94-
});
95-
*/
91+
9692
const fetchSubjectGroupsSuccess = response => ({
9793
type: FETCH_SUBJECT_GROUPS_SUCCESS,
98-
payload: response.data.data,
94+
payload: response?.data?.data ?? [],
9995
});
10096

97+
const fetchSubjectGroupsError = _error => fetchSubjectGroupsSuccess([]);
98+
10199
const INITIAL_MAP_SUBJECT_STATE = {
102100
bbox: null,
103101
subjects: [],

src/sw-build.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const buildSW = () => {
1212
globDirectory: path.join(process.cwd(), 'build'),
1313
globPatterns: ['**/*.{js,html,css,png,svg}'],
1414
globIgnores: ['**/*service-worker*.js', '**/*precache-manifest*.js'],
15-
maximumFileSizeToCacheInBytes: 50 * 1024 * 102,
15+
maximumFileSizeToCacheInBytes: 10 * 1024 * 1024, // 10 MB
1616
})
1717
.then(({ count, size, warnings }) => {
1818
warnings.forEach(console.warn);

0 commit comments

Comments
 (0)