diff --git a/packages/geoview-core/public/locales/en/translation.json b/packages/geoview-core/public/locales/en/translation.json index bd8fc50d048..7bb6977e253 100644 --- a/packages/geoview-core/public/locales/en/translation.json +++ b/packages/geoview-core/public/locales/en/translation.json @@ -16,6 +16,7 @@ }, "mapnav": { "arianavbar": "Vertical button group for map navigation", + "basemap": "Change basemap", "fullscreen": "Full screen", "home": "Reset to initial view", "zoomIn": "Zoom in", @@ -24,6 +25,14 @@ "scale": "Toggle between scale and resolution", "location": "Zoom to my location" }, + "basemaps": { + "select": "Select a basemap", + "default": "Default", + "transport": "Transport", + "imagery": "Imagery", + "simple": "Simple", + "nogeom": "None" + }, "mapctrl": { "rotation": { "resetRotation": "Reset Rotation", @@ -212,4 +221,4 @@ "resizeTooltip": "Resize", "noTab": "No tab" } -} \ No newline at end of file +} diff --git a/packages/geoview-core/public/locales/fr/translation.json b/packages/geoview-core/public/locales/fr/translation.json index 4f89e09115b..cab4cf423f5 100644 --- a/packages/geoview-core/public/locales/fr/translation.json +++ b/packages/geoview-core/public/locales/fr/translation.json @@ -16,6 +16,7 @@ }, "mapnav": { "arianavbar": "Groupe de buttons vertical pour navigation sur la carte", + "basemap": "Changer la carte de base", "fullscreen": "Plein écran", "home": "Retour à la vue initiale", "zoomIn": "Zoom avant", @@ -24,6 +25,14 @@ "scale": "Basculer entre l'échelle et la résolution", "location": "Zoom sur ma position" }, + "basemaps": { + "select": "Choisir une carte de base", + "default": "Défaut", + "transport": "Transport", + "imagery": "Imagerie Satellitaire", + "simple": "Simple", + "nogeom": "Aucun" + }, "mapctrl": { "rotation": { "resetRotation": "Réinitialiser la rotation", @@ -212,4 +221,4 @@ "resizeTooltip": "Redimensionner", "noTab": "Pas d'onglet" } -} \ No newline at end of file +} diff --git a/packages/geoview-core/public/templates/add-panels.html b/packages/geoview-core/public/templates/add-panels.html index 4d3ba572d6d..b9e78d1ff5a 100644 --- a/packages/geoview-core/public/templates/add-panels.html +++ b/packages/geoview-core/public/templates/add-panels.html @@ -1,51 +1,53 @@ - - - - - Add Panels - Canadian Geospatial Platform Viewer - - - - - - - - - - -
- - - - - - - - - - -
-

Add Panels

-
Main
- - - - - - -
This page is used to showcase adding panels to the app-bar and adding panels and buttons to the navbar. -
-
- -
- - - - -
-
+ + Add Panels - Canadian Geospatial Platform Viewer + + + + + + + + + + +
+ + + + + + + + + + +
+

Add Panels

+
Main
+ + + + + + +
This page is used to showcase adding panels to the app-bar and adding panels and buttons to the navbar.
+
+ +
+ + + + +
+
-

- - -

-
-  
-  
-
-
-
\ No newline at end of file
+      }"
+    >
+

+ + +

+
+    
+    
+  
+
diff --git a/packages/geoview-core/src/api/config/types/config-constants.ts b/packages/geoview-core/src/api/config/types/config-constants.ts
index 393257adce1..7c7e6ab745c 100644
--- a/packages/geoview-core/src/api/config/types/config-constants.ts
+++ b/packages/geoview-core/src/api/config/types/config-constants.ts
@@ -173,7 +173,7 @@ export const CV_DEFAULT_MAP_FEATURE_CONFIG = Cast({
     extraOptions: {},
   },
   theme: 'geo.ca',
-  navBar: ['zoom', 'fullscreen', 'home'],
+  navBar: ['zoom', 'fullscreen', 'home', 'basemap-select'],
   footerBar: {
     tabs: {
       core: ['legend', 'layers', 'details', 'data-table'],
diff --git a/packages/geoview-core/src/api/config/types/config-validation-schema.json b/packages/geoview-core/src/api/config/types/config-validation-schema.json
index e36409ce2e0..397d45aa7b3 100644
--- a/packages/geoview-core/src/api/config/types/config-validation-schema.json
+++ b/packages/geoview-core/src/api/config/types/config-validation-schema.json
@@ -90,7 +90,7 @@
     "TypeValidNavBarProps": {
       "description": "Valid values for the navBar array.",
       "additionalProperties": false,
-      "enum": ["zoom", "fullscreen", "home", "location"]
+      "enum": ["zoom", "fullscreen", "home", "location", "basemap-select"]
     },
     "TypeNavBarProps": {
       "description": "Controls available on the navigation bar.",
@@ -100,7 +100,7 @@
       "items": {
         "$ref": "#/definitions/TypeValidNavBarProps"
       },
-      "default": ["zoom", "fullscreen", "home"],
+      "default": ["zoom", "fullscreen", "home", "basemap-select"],
       "minItems": 0
     },
     "TypeValidFooterBarTabsCoreProps": {
diff --git a/packages/geoview-core/src/api/config/types/map-schema-types.ts b/packages/geoview-core/src/api/config/types/map-schema-types.ts
index 26660e67597..8c3a09e74f5 100644
--- a/packages/geoview-core/src/api/config/types/map-schema-types.ts
+++ b/packages/geoview-core/src/api/config/types/map-schema-types.ts
@@ -31,9 +31,9 @@ export { MapFeatureConfig } from '@config/types/classes/map-feature-config';
 export type TypeDisplayTheme = 'dark' | 'light' | 'geo.ca';
 
 /** Valid values for the navBar array. */
-export type TypeValidNavBarProps = 'zoom' | 'fullscreen' | 'home' | 'location';
+export type TypeValidNavBarProps = 'zoom' | 'fullscreen' | 'home' | 'location' | 'basemap-select';
 
-/** Controls available on the navigation bar. Default = ['zoom', 'fullscreen', 'home']. */
+/** Controls available on the navigation bar. Default = ['zoom', 'fullscreen', 'home', 'basemap-select]. */
 export type TypeNavBarProps = TypeValidNavBarProps[];
 
 /** Supported footer bar tabs */
diff --git a/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts b/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts
index 7b4bbe8756e..1c57a61a27a 100644
--- a/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts
+++ b/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts
@@ -672,6 +672,14 @@ export class MapEventProcessor extends AbstractEventProcessor {
     return this.getMapViewer(mapId).basemap.loadDefaultBasemaps(projection, language);
   }
 
+  static async setBasemap(mapId: string, basemapOptions: TypeBasemapOptions): Promise {
+    // Set basemap will use the current display language and projection and recreate the basemap
+    const language = AppEventProcessor.getDisplayLanguage(mapId);
+    const projection = this.getMapState(mapId).currentProjection as TypeValidMapProjectionCodes;
+    const basemap = await this.getMapViewer(mapId).basemap.createCoreBasemap(basemapOptions, projection, language);
+    if (basemap) this.getMapViewer(mapId).basemap.setBasemap(basemap);
+  }
+
   static setMapKeyboardPanInteractions(mapId: string, panDelta: number): void {
     const mapElement = this.getMapViewer(mapId).map;
 
diff --git a/packages/geoview-core/src/core/components/index.ts b/packages/geoview-core/src/core/components/index.ts
index aa1e215f327..89df9c71599 100644
--- a/packages/geoview-core/src/core/components/index.ts
+++ b/packages/geoview-core/src/core/components/index.ts
@@ -30,6 +30,7 @@ export * from './layers/layers-panel';
 export * from './lightbox/lightbox';
 export * from './map/map';
 export * from './mouse-position/mouse-position';
+export * from './nav-bar/buttons/basemap-select';
 export * from './nav-bar/buttons/fullscreen';
 export * from './nav-bar/buttons/home';
 export * from './nav-bar/buttons/location';
diff --git a/packages/geoview-core/src/core/components/nav-bar/buttons/basemap-select.tsx b/packages/geoview-core/src/core/components/nav-bar/buttons/basemap-select.tsx
new file mode 100644
index 00000000000..5d684139990
--- /dev/null
+++ b/packages/geoview-core/src/core/components/nav-bar/buttons/basemap-select.tsx
@@ -0,0 +1,152 @@
+import React, { createElement, ReactNode, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useMapBasemapOptions, useMapStoreActions } from '@/core/stores/store-interface-and-intial-values/map-state';
+import { MapIcon } from '@/ui';
+import { logger } from '@/core/utils/logger';
+import { TypeBasemapOptions } from '@/api/config/types/map-schema-types';
+import NavbarPanelButton from '../nav-bar-panel-button';
+import { TypePanelProps } from '@/ui/panel/panel-types';
+import { TypeIconButtonProps } from '@/ui/icon-button/icon-button-types';
+import { List, ListItem } from '@/ui/list';
+import { IconButton } from '@/ui/icon-button/icon-button';
+import { BlockIcon, PublicIcon, SatelliteIcon, SignpostIcon } from '@/ui/icons';
+
+const basemapChoiceOptions: Record = {
+  transport: { basemapId: 'transport', shaded: true, labeled: true },
+  imagery: { basemapId: 'imagery', shaded: false, labeled: false },
+  simple: { basemapId: 'simple', shaded: false, labeled: false },
+  nogeom: { basemapId: 'nogeom', shaded: false, labeled: false },
+};
+
+/**
+ * Create a basemap select button to open the select panel, and set panel content
+ * @returns {JSX.Element} the created basemap select button
+ */
+export default function BasemapSelect(): JSX.Element {
+  // Log
+  logger.logTraceRender('components/nav-bar/buttons/basemap');
+
+  const { t } = useTranslation();
+
+  // Get values from store
+  const { createBaseMapFromOptions } = useMapStoreActions();
+  const configBasemapOptions = useMapBasemapOptions();
+
+  // Check if the basemap from the config is one of our default basemaps
+  const noDefault =
+    Object.keys(basemapChoiceOptions).includes(configBasemapOptions.basemapId) &&
+    JSON.stringify(configBasemapOptions) === JSON.stringify(basemapChoiceOptions[configBasemapOptions.basemapId]);
+
+  const [selectedBasemap, setSelectedBasemap] = useState(noDefault ? configBasemapOptions.basemapId : 'default');
+
+  /**
+   * Handles basemap selection and updates basemap
+   * @returns {JSX.Element} the created basemap select button
+   */
+  const handleChoice = (basemapChoice: string): void => {
+    setSelectedBasemap(basemapChoice);
+    createBaseMapFromOptions(basemapChoice === 'default' ? configBasemapOptions : basemapChoiceOptions[basemapChoice]).catch((error) => {
+      // Log
+      logger.logPromiseFailed('setBaseMap in basemaps.ts', error);
+    });
+  };
+
+  // Set up props for nav bar panel button
+  const button: TypeIconButtonProps = {
+    tooltip: 'mapnav.basemap',
+    children: createElement(MapIcon),
+    tooltipPlacement: 'left',
+  };
+
+  /**
+   * Render buttons in navbar panel.
+   * @returns ReactNode
+   */
+  const renderButtons = (): ReactNode => {
+    return (
+      
+        {!noDefault && (
+          
+             handleChoice('default')}
+              disabled={selectedBasemap === 'default'}
+            >
+              
+              {t('basemaps.default')}
+            
+          
+        )}
+        
+           handleChoice('transport')}
+            disabled={selectedBasemap === 'transport'}
+          >
+            
+            {t('basemaps.transport')}
+          
+        
+        
+           handleChoice('imagery')}
+            disabled={selectedBasemap === 'imagery'}
+          >
+            
+            {t('basemaps.imagery')}
+          
+        
+        
+           handleChoice('simple')}
+            disabled={selectedBasemap === 'simple'}
+          >
+            
+            {t('basemaps.simple')}
+          
+        
+        
+           handleChoice('nogeom')}
+            disabled={selectedBasemap === 'nogeom'}
+          >
+            
+            {t('basemaps.nogeom')}
+          
+        
+      
+    );
+  };
+
+  const panel: TypePanelProps = {
+    title: 'Select a basemap',
+    icon: createElement(MapIcon),
+    content: renderButtons(),
+    width: 'flex',
+  };
+
+  return ;
+}
diff --git a/packages/geoview-core/src/core/components/nav-bar/nav-bar-panel-button.tsx b/packages/geoview-core/src/core/components/nav-bar/nav-bar-panel-button.tsx
index e7e97805a2b..d5df012165b 100644
--- a/packages/geoview-core/src/core/components/nav-bar/nav-bar-panel-button.tsx
+++ b/packages/geoview-core/src/core/components/nav-bar/nav-bar-panel-button.tsx
@@ -50,9 +50,6 @@ export default function NavbarPanelButton({ buttonPanel }: NavbarPanelButtonType
     }
   };
 
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  const panelContent = (buttonPanel.panel?.content ?? '') as any;
-
   return (
     
       
@@ -83,7 +80,11 @@ export default function NavbarPanelButton({ buttonPanel }: NavbarPanelButtonType
           
             {(buttonPanel.panel?.title as string) ?? ''}
             
-              
+              {buttonPanel.panel?.convertHtmlContent ? (
+                
+              ) : (
+                buttonPanel.panel?.content
+              )}
             
           
         
diff --git a/packages/geoview-core/src/core/components/nav-bar/nav-bar.tsx b/packages/geoview-core/src/core/components/nav-bar/nav-bar.tsx
index e887f85b7ea..bed4b708b1c 100644
--- a/packages/geoview-core/src/core/components/nav-bar/nav-bar.tsx
+++ b/packages/geoview-core/src/core/components/nav-bar/nav-bar.tsx
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
 
 import { useTheme } from '@mui/material/styles';
 
+import BasemapSelect from './buttons/basemap-select';
 import ZoomIn from './buttons/zoom-in';
 import ZoomOut from './buttons/zoom-out';
 import Fullscreen from './buttons/fullscreen';
@@ -21,7 +22,7 @@ type NavBarProps = {
   api: NavBarApi;
 };
 
-type DefaultNavbar = 'fullScreen' | 'location' | 'home' | 'zoomIn' | 'zoomOut';
+type DefaultNavbar = 'fullScreen' | 'location' | 'home' | 'zoomIn' | 'zoomOut' | 'basemapSelect';
 type NavbarButtonGroup = Record;
 type NavButtonGroups = Record;
 
@@ -46,6 +47,7 @@ export function NavBar(props: NavBarProps): JSX.Element {
     fullScreen: ,
     location: ,
     home: ,
+    basemapSelect: ,
     zoomIn: ,
     zoomOut: ,
   };
@@ -74,10 +76,16 @@ export function NavBar(props: NavBarProps): JSX.Element {
       displayButtons = { ...displayButtons, home: 'home' };
     }
 
+    if (navBarComponents.includes('basemap-select')) {
+      displayButtons = { ...displayButtons, basemapSelect: 'basemapSelect' };
+    }
+
     setButtonPanelGroups({
       ...{ display: displayButtons },
       ...buttonPanelGroups,
     });
+    // If buttonPanelGroups is in the dependencies, it triggers endless rerenders
+    // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [navBarComponents]);
 
   const handleNavApiAddButtonPanel = useCallback(
diff --git a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/map-state.ts b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/map-state.ts
index e400a7cebb6..5c41c404bab 100644
--- a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/map-state.ts
+++ b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/map-state.ts
@@ -53,7 +53,7 @@ export interface IMapState {
   setDefaultConfigValues: (config: TypeMapFeaturesConfig) => void;
 
   actions: {
-    createBaseMapFromOptions: () => Promise;
+    createBaseMapFromOptions: (basemapOptions: TypeBasemapOptions) => Promise;
     getPixelFromCoordinate: (coord: Coordinate) => [number, number];
     getIndexFromOrderedLayerInfo: (layerPath: string) => number;
     getLegendCollapsedFromOrderedLayerInfo: (layerPath: string) => boolean;
@@ -64,6 +64,7 @@ export interface IMapState {
     addHighlightedFeature: (feature: TypeFeatureInfoEntry) => void;
     removeHighlightedFeature: (feature: TypeFeatureInfoEntry | 'all') => void;
     reorderLayer: (layerPath: string, move: number) => void;
+    resetBaseMap: () => Promise;
     setLegendCollapsed: (layerPath: string, newValue?: boolean) => void;
     setOrToggleLayerVisibility: (layerPath: string, newValue?: boolean) => void;
     setMapKeyboardPanInteractions: (panDelta: number) => void;
@@ -174,12 +175,13 @@ export function initializeMapState(set: TypeSetStore, get: TypeGetStore): IMapSt
 
     actions: {
       /**
-       * Resets the base map.
+       * Sets new base map from options.
+       * @param {TypeBasemapOptions} basemapOptions - The options for the new basemap
        * @returns {Promise}
        */
-      createBaseMapFromOptions: (): Promise => {
+      createBaseMapFromOptions: (basemapOptions: TypeBasemapOptions): Promise => {
         // Redirect to processor
-        return MapEventProcessor.resetBasemap(get().mapId);
+        return MapEventProcessor.setBasemap(get().mapId, basemapOptions);
       },
 
       /**
@@ -277,6 +279,15 @@ export function initializeMapState(set: TypeSetStore, get: TypeGetStore): IMapSt
         MapEventProcessor.reorderLayer(get().mapId, layerPath, move);
       },
 
+      /**
+       * Resets the base map.
+       * @returns {Promise}
+       */
+      resetBaseMap: (): Promise => {
+        // Redirect to processor
+        return MapEventProcessor.resetBasemap(get().mapId);
+      },
+
       /**
        * Sets or toggles the legend of a layer.
        * @param {string} layerPath - The path of the layer.
diff --git a/packages/geoview-core/src/ui/icons/index.ts b/packages/geoview-core/src/ui/icons/index.ts
index cff7b85ecc0..8e91bbf21c2 100644
--- a/packages/geoview-core/src/ui/icons/index.ts
+++ b/packages/geoview-core/src/ui/icons/index.ts
@@ -13,6 +13,7 @@ export {
   ArrowLeft as ArrowLeftIcon,
   ArrowRight as ArrowRightIcon,
   ArrowUpward as ArrowUpIcon,
+  Block as BlockIcon,
   BrowserNotSupported as BrowserNotSupportedIcon,
   Check as CheckIcon,
   CheckCircle as CheckCircleIcon,
@@ -77,6 +78,7 @@ export {
   OpenInBrowser as OpenInBrowserIcon,
   Pause as PauseIcon,
   PlayArrow as PlayArrowIcon,
+  Public as PublicIcon,
   QueryStats as ChartIcon,
   QuestionMark as QuestionMarkIcon,
   RadioButtonChecked as RadioButtonCheckedIcon,
@@ -84,9 +86,11 @@ export {
   Remove as ZoomOutIcon,
   RemoveCircleOutline as RemoveCircleOutlineIcon,
   RestartAlt as RestartAltIcon,
+  Satellite as SatelliteIcon,
   School as SchoolIcon,
   Search as SearchIcon,
   Send as SendIcon,
+  Signpost as SignpostIcon,
   Storage as StorageIcon,
   SwitchRight as SwitchRightIcon,
   SwitchLeft as SwitchLeftIcon,
diff --git a/packages/geoview-core/src/ui/panel/panel-types.ts b/packages/geoview-core/src/ui/panel/panel-types.ts
index 62f54dd4a2a..b2ef481adf3 100644
--- a/packages/geoview-core/src/ui/panel/panel-types.ts
+++ b/packages/geoview-core/src/ui/panel/panel-types.ts
@@ -34,6 +34,8 @@ export type TypePanelProps = {
   title: string | TypeJsonValue;
   /** Panel body content. */
   content?: ReactNode;
+  /** Convert panel body content from HTML. */
+  convertHtmlContent?: boolean;
   /** Custom panel styles */
   panelStyles?: PanelStyles;
 };