Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,52 @@ const config = {
// ]
// }
// }

// Configure custom basemaps. You have three options:
// 1. Replace all default basemaps with your own
// 2. Add custom basemaps to the existing defaults
// 3. Disable specific default basemaps
//
// basemaps: {
// // Option 1: Replace all default basemaps with custom ones
// basemaps: [
// {
// name: 'My Custom Raster',
// type: 'raster',
// url: ['https://example.com/tiles/{z}/{x}/{y}.png'],
// attribution: '© My Custom Provider',
// maxZoom: 18
// },
// {
// name: 'My Custom Vector',
// type: 'vector',
// url: 'https://example.com/style.json?key={maptiler_key}',
// attribution: '© My Vector Provider'
// }
// ],
//
// // Option 2: Add custom basemaps to defaults (commented out when using Option 1)
// // customBasemaps: [
// // {
// // name: 'Additional Custom Map',
// // type: 'raster',
// // url: ['https://another-provider.com/{z}/{x}/{y}{retina_suffix}.png?key={thunderforest_key}'],
// // attribution: '© Another Provider',
// // maxZoom: 19,
// // tilePixelRatio: 2 // Optional: override retina detection
// // }
// // ],
//
// // Option 3: Disable specific default basemaps by name (commented out when using Option 1)
// // disabledBasemaps: ['TF Transport', 'TF Cycle', 'TF Outdoors']
// }
//
// Available API key placeholders for URLs:
// - {omniscale_key} - replaced with keys.omniscale
// - {maptiler_key} - replaced with keys.maptiler
// - {thunderforest_key} - replaced with keys.thunderforest
// - {kurviger_key} - replaced with keys.kurviger
// - {retina_suffix} - replaced with '@2x' on retina displays, empty otherwise
}

// this is needed for jest (with our current setup at least)
Expand Down
17 changes: 17 additions & 0 deletions src/custom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ declare module 'config' {
readonly options: { profile: string }[]
}

interface BasemapConfig {
name: string
type: 'raster' | 'vector'
url: string[] | string
attribution: string
maxZoom?: number
tilePixelRatio?: number
}

const routingApi: string
const geocodingApi: string
const defaultTiles: string
Expand Down Expand Up @@ -37,6 +46,14 @@ declare module 'config' {
}
const profile_group_mapping: Record<string, ProfileGroup>
const profiles: object
const basemaps: {
// Replace all default basemaps with custom ones
basemaps?: BasemapConfig[]
// Add custom basemaps to the default ones
customBasemaps?: BasemapConfig[]
// Disable specific default basemaps by name
disabledBasemaps?: string[]
} | undefined
}

declare module 'react-responsive' {
Expand Down
70 changes: 68 additions & 2 deletions src/stores/MapOptionsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ const wanderreitkarte: RasterStyle = {
maxZoom: 18,
}

const styleOptions: StyleOption[] = [
// Default basemaps - these will be used as fallback when no custom configuration is provided
const defaultBasemaps: StyleOption[] = [
omniscale,
osmOrg,
osmCycl,
Expand All @@ -169,6 +170,71 @@ const styleOptions: StyleOption[] = [
wanderreitkarte,
]

// Function to create basemap configurations from config
function createBasemapsFromConfig(): StyleOption[] {
// If custom basemaps are completely defined, use them instead of defaults
if (config.basemaps?.basemaps) {
return config.basemaps.basemaps.map(convertConfigBasemapToStyleOption)
}

// Start with default basemaps
let basemaps = [...defaultBasemaps]

// Remove disabled basemaps if specified
if (config.basemaps?.disabledBasemaps) {
basemaps = basemaps.filter(basemap => !config.basemaps!.disabledBasemaps!.includes(basemap.name))
}

// Add custom basemaps if specified
if (config.basemaps?.customBasemaps) {
const customBasemaps = config.basemaps.customBasemaps.map(convertConfigBasemapToStyleOption)
basemaps = [...basemaps, ...customBasemaps]
}

return basemaps
}

// Convert config basemap to StyleOption, handling API key interpolation and retina detection
function convertConfigBasemapToStyleOption(configBasemap: any): StyleOption {
let processedUrl = configBasemap.url

// Handle API key interpolation for URLs
if (typeof processedUrl === 'string') {
processedUrl = interpolateApiKeys(processedUrl)
} else if (Array.isArray(processedUrl)) {
processedUrl = processedUrl.map(interpolateApiKeys)
}

const styleOption: StyleOption = {
name: configBasemap.name,
type: configBasemap.type,
url: processedUrl,
attribution: configBasemap.attribution,
maxZoom: configBasemap.maxZoom,
}

// Add tilePixelRatio for raster styles if specified or use default retina detection
if (configBasemap.type === 'raster') {
const rasterStyle = styleOption as RasterStyle
rasterStyle.tilePixelRatio = configBasemap.tilePixelRatio ?? tilePixelRatio
}

return styleOption
}

// Helper function to interpolate API keys in URLs
function interpolateApiKeys(url: string): string {
return url
.replace('{omniscale_key}', osApiKey)
.replace('{maptiler_key}', mapTilerKey)
.replace('{thunderforest_key}', thunderforestApiKey)
.replace('{kurviger_key}', kurvigerApiKey)
.replace('{retina_suffix}', retina2x)
}

// Create the final styleOptions array
const styleOptions: StyleOption[] = createBasemapsFromConfig()

export default class MapOptionsStore extends Store<MapOptionsStoreState> {
constructor() {
super(MapOptionsStore.getInitialState())
Expand All @@ -181,7 +247,7 @@ export default class MapOptionsStore extends Store<MapOptionsStoreState> {
`Could not find tile layer specified in config: '${config.defaultTiles}', using default instead`,
)
return {
selectedStyle: selectedStyle ? selectedStyle : omniscale,
selectedStyle: selectedStyle ? selectedStyle : styleOptions[0],
styleOptions,
routingGraphEnabled: false,
urbanDensityEnabled: false,
Expand Down