@aguacerowx/mapsgl 0.0.57 → 0.0.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/MapManager.js CHANGED
@@ -1,198 +1,198 @@
1
- // aguacero-api/src/MapManager.js
2
-
3
- import { THEME_CONFIGS } from '@aguacerowx/javascript-sdk';
4
- import { applyStyleCustomizations } from './style-applicator.js';
5
- import { STYLE_LAYER_MAP } from './style-layer-map.js';
6
- import { EventEmitter } from '@aguacerowx/javascript-sdk';
7
-
8
- /**
9
- * A utility to recursively merge two objects.
10
- * The source object's properties will overwrite the target's properties.
11
- * This is a "non-mutating" version; it returns a new object.
12
- * @param {object} target - The base object.
13
- * @param {object} source - The object with properties to merge in.
14
- * @returns {object} A new, merged object.
15
- */
16
- function deepMerge(target, source) {
17
- const output = { ...target };
18
-
19
- if (isObject(target) && isObject(source)) {
20
- Object.keys(source).forEach(key => {
21
- if (isObject(source[key])) {
22
- if (!(key in target)) {
23
- Object.assign(output, { [key]: source[key] });
24
- } else {
25
- output[key] = deepMerge(target[key], source[key]);
26
- }
27
- } else {
28
- Object.assign(output, { [key]: source[key] });
29
- }
30
- });
31
- }
32
-
33
- return output;
34
- }
35
- // Helper for the deepMerge utility
36
- function isObject(item) {
37
- return (item && typeof item === 'object' && !Array.isArray(item));
38
- }
39
-
40
-
41
- const BASE_STYLE_URL = 'mapbox://styles/aguacerowx/cmfvox8mq004u01qm5nlg7qkt';
42
-
43
- export class MapManager extends EventEmitter {
44
- /**
45
- * @param {string} containerId - DOM element id for the Mapbox map container.
46
- * @param {object} options
47
- * @param {string} [options.accessToken] - Mapbox access token (required unless `mapboxToken` is set).
48
- * @param {string} [options.mapboxToken] - Alias for `accessToken` (e.g. React Native parity).
49
- * @param {object} [options.mapOptions] - Passed through to `mapboxgl.Map` (after defaults); `style` here is used unless a valid custom style + anchor pair is set.
50
- * @param {object} [options.customStyles] - Per-theme style overrides merged into {@link THEME_CONFIGS}.
51
- * @param {'light'|'dark'} [options.defaultTheme]
52
- * @param {'light'|'dark'} [options.theme] - Alias for `defaultTheme`.
53
- * @param {string} [options.styleUrl] - Custom Mapbox style URL (alias: `styleURL`).
54
- * @param {string} [options.styleURL] - Same as `styleUrl`.
55
- * @param {string} [options.weatherBeforeLayerId] - Style layer id to insert Aguacero layers below (alias: `belowID`).
56
- * @param {string} [options.belowID] - Same as `weatherBeforeLayerId`. With `styleUrl`/`styleURL`, required for correct stacking; default Aguacero style uses `AML_-_terrain` in {@link WeatherLayerManager} when omitted.
57
- */
58
- constructor(containerId, options = {}) {
59
- super();
60
- const accessToken = options.accessToken || options.mapboxToken;
61
- if (!containerId || !accessToken) {
62
- throw new Error('A container ID and a Mapbox access token are required.');
63
- }
64
-
65
- mapboxgl.accessToken = accessToken;
66
-
67
- // --- THE FIX IS HERE ---
68
-
69
- // 1. Start with clean copies of the base themes
70
- let lightTheme = JSON.parse(JSON.stringify(THEME_CONFIGS.light));
71
- let darkTheme = JSON.parse(JSON.stringify(THEME_CONFIGS.dark));
72
-
73
- // 2. If developer provides custom styles, merge them into the defaults
74
- if (options.customStyles) {
75
- if (options.customStyles.light) {
76
- lightTheme = deepMerge(lightTheme, options.customStyles.light);
77
- }
78
- if (options.customStyles.dark) {
79
- darkTheme = deepMerge(darkTheme, options.customStyles.dark);
80
- }
81
- }
82
-
83
- // 3. Store the final, potentially merged, themes
84
- this.themes = {
85
- light: lightTheme,
86
- dark: darkTheme
87
- };
88
-
89
- // --- END OF FIX ---
90
-
91
- const defaultThemeName = options.theme || options.defaultTheme || 'light';
92
- this.currentCustomizations = this.themes[defaultThemeName];
93
- this.currentThemeName = defaultThemeName;
94
-
95
- this.weatherLayerManagers = new Map();
96
-
97
- const mapOptions = options.mapOptions || {};
98
- let initialStyle = mapOptions.style !== undefined ? mapOptions.style : BASE_STYLE_URL;
99
- /** @type {string | null} */
100
- let weatherBeforeLayerId = null;
101
-
102
- const customStyleUrl = options.styleURL || options.styleUrl;
103
- const customBeforeId = options.belowID || options.weatherBeforeLayerId;
104
- if (customStyleUrl) {
105
- if (customBeforeId && String(customBeforeId).trim()) {
106
- initialStyle = customStyleUrl;
107
- weatherBeforeLayerId = String(customBeforeId).trim();
108
- } else {
109
- console.warn(
110
- '[MapManager] A custom style (`styleUrl` / `styleURL`) was provided without a non-empty `weatherBeforeLayerId` / `belowID`. ' +
111
- 'Weather layers need a style layer id to insert below; keeping the default Aguacero style.'
112
- );
113
- if (mapOptions.style === undefined) {
114
- initialStyle = BASE_STYLE_URL;
115
- }
116
- }
117
- }
118
-
119
- this.weatherBeforeLayerId = weatherBeforeLayerId;
120
-
121
- this.map = new mapboxgl.Map({
122
- container: containerId,
123
- center: [-98, 39],
124
- zoom: 3.5,
125
- ...mapOptions,
126
- style: initialStyle,
127
- });
128
-
129
- this.map.__aguaceroMapsgl = { weatherBeforeLayerId };
130
-
131
- this.map.on('load', () => {
132
- applyStyleCustomizations(this.map, this.currentCustomizations);
133
- this.emit('style:applied', {
134
- themeName: this.currentThemeName,
135
- styles: this.currentCustomizations
136
- });
137
- });
138
- }
139
-
140
- // The rest of the methods (setTheme, setLabelGroupVisibility, etc.) are correct and remain unchanged...
141
- setTheme(themeName) {
142
- if (!this.themes[themeName]) {
143
- return;
144
- }
145
-
146
- const newThemeStyles = JSON.parse(JSON.stringify(this.themes[themeName]));
147
- const oldLabelStyles = this.currentCustomizations.labels;
148
-
149
- if (oldLabelStyles) {
150
- for (const category in oldLabelStyles) {
151
- if (oldLabelStyles[category]?.hasOwnProperty('visible') && newThemeStyles.labels[category]) {
152
- newThemeStyles.labels[category].visible = oldLabelStyles[category].visible;
153
- }
154
- for (const subKey in oldLabelStyles[category]) {
155
- if (oldLabelStyles[category][subKey]?.hasOwnProperty('visible') && newThemeStyles.labels[category]?.[subKey]) {
156
- newThemeStyles.labels[category][subKey].visible = oldLabelStyles[category][subKey].visible;
157
- }
158
- }
159
- }
160
- }
161
-
162
- this.currentCustomizations = newThemeStyles;
163
- this.currentThemeName = themeName;
164
-
165
- applyStyleCustomizations(this.map, this.currentCustomizations);
166
-
167
- this.emit('style:applied', {
168
- themeName: this.currentThemeName,
169
- styles: this.currentCustomizations
170
- });
171
- }
172
-
173
- setLabelGroupVisibility(groupKey, visible) {
174
- const path = `labels.${groupKey}.visible`;
175
- let current = this.currentCustomizations;
176
- const parts = path.split('.');
177
- for (let i = 0; i < parts.length - 1; i++) {
178
- current = current[parts[i]];
179
- if (!current) {
180
- return;
181
- }
182
- }
183
- current[parts[parts.length - 1]] = visible;
184
-
185
- const mapKey = groupKey.replace(/\.(.)/g, (match, p1) => p1.toUpperCase());
186
- const layerId = STYLE_LAYER_MAP[mapKey]?.layerId;
187
-
188
- if (layerId && this.map.getLayer(layerId)) {
189
- this.map.setLayoutProperty(layerId, 'visibility', visible ? 'visible' : 'none');
190
- }
191
- }
192
-
193
- addWeatherManager(manager) {
194
- this.weatherLayerManagers.set(manager.layerId, manager);
195
- }
196
-
197
- getMap() { return this.map; }
1
+ // aguacero-api/src/MapManager.js
2
+
3
+ import { THEME_CONFIGS } from '@aguacerowx/javascript-sdk';
4
+ import { applyStyleCustomizations } from './style-applicator.js';
5
+ import { STYLE_LAYER_MAP } from './style-layer-map.js';
6
+ import { EventEmitter } from '@aguacerowx/javascript-sdk';
7
+
8
+ /**
9
+ * A utility to recursively merge two objects.
10
+ * The source object's properties will overwrite the target's properties.
11
+ * This is a "non-mutating" version; it returns a new object.
12
+ * @param {object} target - The base object.
13
+ * @param {object} source - The object with properties to merge in.
14
+ * @returns {object} A new, merged object.
15
+ */
16
+ function deepMerge(target, source) {
17
+ const output = { ...target };
18
+
19
+ if (isObject(target) && isObject(source)) {
20
+ Object.keys(source).forEach(key => {
21
+ if (isObject(source[key])) {
22
+ if (!(key in target)) {
23
+ Object.assign(output, { [key]: source[key] });
24
+ } else {
25
+ output[key] = deepMerge(target[key], source[key]);
26
+ }
27
+ } else {
28
+ Object.assign(output, { [key]: source[key] });
29
+ }
30
+ });
31
+ }
32
+
33
+ return output;
34
+ }
35
+ // Helper for the deepMerge utility
36
+ function isObject(item) {
37
+ return (item && typeof item === 'object' && !Array.isArray(item));
38
+ }
39
+
40
+
41
+ const BASE_STYLE_URL = 'mapbox://styles/aguacerowx/cmfvox8mq004u01qm5nlg7qkt';
42
+
43
+ export class MapManager extends EventEmitter {
44
+ /**
45
+ * @param {string} containerId - DOM element id for the Mapbox map container.
46
+ * @param {object} options
47
+ * @param {string} [options.accessToken] - Mapbox access token (required unless `mapboxToken` is set).
48
+ * @param {string} [options.mapboxToken] - Alias for `accessToken` (e.g. React Native parity).
49
+ * @param {object} [options.mapOptions] - Passed through to `mapboxgl.Map` (after defaults); `style` here is used unless a valid custom style + anchor pair is set.
50
+ * @param {object} [options.customStyles] - Per-theme style overrides merged into {@link THEME_CONFIGS}.
51
+ * @param {'light'|'dark'} [options.defaultTheme]
52
+ * @param {'light'|'dark'} [options.theme] - Alias for `defaultTheme`.
53
+ * @param {string} [options.styleUrl] - Custom Mapbox style URL (alias: `styleURL`).
54
+ * @param {string} [options.styleURL] - Same as `styleUrl`.
55
+ * @param {string} [options.weatherBeforeLayerId] - Style layer id to insert Aguacero layers below (alias: `belowID`).
56
+ * @param {string} [options.belowID] - Same as `weatherBeforeLayerId`. With `styleUrl`/`styleURL`, required for correct stacking; default Aguacero style uses `AML_-_terrain` in {@link WeatherLayerManager} when omitted.
57
+ */
58
+ constructor(containerId, options = {}) {
59
+ super();
60
+ const accessToken = options.accessToken || options.mapboxToken;
61
+ if (!containerId || !accessToken) {
62
+ throw new Error('A container ID and a Mapbox access token are required.');
63
+ }
64
+
65
+ mapboxgl.accessToken = accessToken;
66
+
67
+ // --- THE FIX IS HERE ---
68
+
69
+ // 1. Start with clean copies of the base themes
70
+ let lightTheme = JSON.parse(JSON.stringify(THEME_CONFIGS.light));
71
+ let darkTheme = JSON.parse(JSON.stringify(THEME_CONFIGS.dark));
72
+
73
+ // 2. If developer provides custom styles, merge them into the defaults
74
+ if (options.customStyles) {
75
+ if (options.customStyles.light) {
76
+ lightTheme = deepMerge(lightTheme, options.customStyles.light);
77
+ }
78
+ if (options.customStyles.dark) {
79
+ darkTheme = deepMerge(darkTheme, options.customStyles.dark);
80
+ }
81
+ }
82
+
83
+ // 3. Store the final, potentially merged, themes
84
+ this.themes = {
85
+ light: lightTheme,
86
+ dark: darkTheme
87
+ };
88
+
89
+ // --- END OF FIX ---
90
+
91
+ const defaultThemeName = options.theme || options.defaultTheme || 'light';
92
+ this.currentCustomizations = this.themes[defaultThemeName];
93
+ this.currentThemeName = defaultThemeName;
94
+
95
+ this.weatherLayerManagers = new Map();
96
+
97
+ const mapOptions = options.mapOptions || {};
98
+ let initialStyle = mapOptions.style !== undefined ? mapOptions.style : BASE_STYLE_URL;
99
+ /** @type {string | null} */
100
+ let weatherBeforeLayerId = null;
101
+
102
+ const customStyleUrl = options.styleURL || options.styleUrl;
103
+ const customBeforeId = options.belowID || options.weatherBeforeLayerId;
104
+ if (customStyleUrl) {
105
+ if (customBeforeId && String(customBeforeId).trim()) {
106
+ initialStyle = customStyleUrl;
107
+ weatherBeforeLayerId = String(customBeforeId).trim();
108
+ } else {
109
+ console.warn(
110
+ '[MapManager] A custom style (`styleUrl` / `styleURL`) was provided without a non-empty `weatherBeforeLayerId` / `belowID`. ' +
111
+ 'Weather layers need a style layer id to insert below; keeping the default Aguacero style.'
112
+ );
113
+ if (mapOptions.style === undefined) {
114
+ initialStyle = BASE_STYLE_URL;
115
+ }
116
+ }
117
+ }
118
+
119
+ this.weatherBeforeLayerId = weatherBeforeLayerId;
120
+
121
+ this.map = new mapboxgl.Map({
122
+ container: containerId,
123
+ center: [-98, 39],
124
+ zoom: 3.5,
125
+ ...mapOptions,
126
+ style: initialStyle,
127
+ });
128
+
129
+ this.map.__aguaceroMapsgl = { weatherBeforeLayerId };
130
+
131
+ this.map.on('load', () => {
132
+ applyStyleCustomizations(this.map, this.currentCustomizations);
133
+ this.emit('style:applied', {
134
+ themeName: this.currentThemeName,
135
+ styles: this.currentCustomizations
136
+ });
137
+ });
138
+ }
139
+
140
+ // The rest of the methods (setTheme, setLabelGroupVisibility, etc.) are correct and remain unchanged...
141
+ setTheme(themeName) {
142
+ if (!this.themes[themeName]) {
143
+ return;
144
+ }
145
+
146
+ const newThemeStyles = JSON.parse(JSON.stringify(this.themes[themeName]));
147
+ const oldLabelStyles = this.currentCustomizations.labels;
148
+
149
+ if (oldLabelStyles) {
150
+ for (const category in oldLabelStyles) {
151
+ if (oldLabelStyles[category]?.hasOwnProperty('visible') && newThemeStyles.labels[category]) {
152
+ newThemeStyles.labels[category].visible = oldLabelStyles[category].visible;
153
+ }
154
+ for (const subKey in oldLabelStyles[category]) {
155
+ if (oldLabelStyles[category][subKey]?.hasOwnProperty('visible') && newThemeStyles.labels[category]?.[subKey]) {
156
+ newThemeStyles.labels[category][subKey].visible = oldLabelStyles[category][subKey].visible;
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ this.currentCustomizations = newThemeStyles;
163
+ this.currentThemeName = themeName;
164
+
165
+ applyStyleCustomizations(this.map, this.currentCustomizations);
166
+
167
+ this.emit('style:applied', {
168
+ themeName: this.currentThemeName,
169
+ styles: this.currentCustomizations
170
+ });
171
+ }
172
+
173
+ setLabelGroupVisibility(groupKey, visible) {
174
+ const path = `labels.${groupKey}.visible`;
175
+ let current = this.currentCustomizations;
176
+ const parts = path.split('.');
177
+ for (let i = 0; i < parts.length - 1; i++) {
178
+ current = current[parts[i]];
179
+ if (!current) {
180
+ return;
181
+ }
182
+ }
183
+ current[parts[parts.length - 1]] = visible;
184
+
185
+ const mapKey = groupKey.replace(/\.(.)/g, (match, p1) => p1.toUpperCase());
186
+ const layerId = STYLE_LAYER_MAP[mapKey]?.layerId;
187
+
188
+ if (layerId && this.map.getLayer(layerId)) {
189
+ this.map.setLayoutProperty(layerId, 'visibility', visible ? 'visible' : 'none');
190
+ }
191
+ }
192
+
193
+ addWeatherManager(manager) {
194
+ this.weatherLayerManagers.set(manager.layerId, manager);
195
+ }
196
+
197
+ getMap() { return this.map; }
198
198
  }