Skip to content

Commit 8b9e96d

Browse files
committed
fix(Map): resolve Stamen tile 401 errors and add free tile providers
Tile Provider Updates: - Add 3 new free tile providers (no API key required): - osm-france: French-language OpenStreetMap style - osm-bw: Black & white monochrome OSM variant - wikimedia: Wikimedia Foundation tile service - Update Stamen provider configs to require API keys: - Mark stamen-toner, stamen-terrain, stamen-watercolor as requiresApiKey: true - Update URLs with ?api_key={apiKey} parameter - Update documentation to reflect authentication requirement Storybook Changes: - Remove Stamen tile examples from public Storybook demos - Prevents 401 errors on GitHub Pages deployment - Stadia Maps allows localhost without auth but requires API keys for production domains - Update Premium Providers documentation section: - Add Stamen (via Stadia Maps) with signup link - Document Geoapify and Thunderforest providers - Show code examples with tileApiKey prop usage - Update intro text: 15+ → 18+ tile providers supported - Add note clarifying that examples show free providers only Why this fixes the issue: - Stadia Maps (hosting Stamen tiles) allows unauthenticated localhost requests for development - Production domains (like GitHub Pages) require API key authentication - This explains why tiles work locally but return 401 on deployed Storybook Result: - Library now supports 18 tile providers (up from 15) - Public Storybook shows only free providers (no 401 errors) - Users with API keys can still use premium providers via tileApiKey prop - Clear documentation on authentication requirements
1 parent 8a03ab1 commit 8b9e96d

File tree

2 files changed

+115
-91
lines changed

2 files changed

+115
-91
lines changed

packages/ui/src/components/Map/TileProviders.stories.tsx

Lines changed: 63 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,6 @@ export const TileProviders: FC = () => {
110110
const [selectedId1, setSelectedId1] = useState<string | undefined>(undefined);
111111
const [selectedId2, setSelectedId2] = useState<string | undefined>(undefined);
112112
const [selectedId3, setSelectedId3] = useState<string | undefined>(undefined);
113-
const [selectedId4, setSelectedId4] = useState<string | undefined>(undefined);
114-
const [selectedId5, setSelectedId5] = useState<string | undefined>(undefined);
115-
const [selectedId6, setSelectedId6] = useState<string | undefined>(undefined);
116113
const [selectedId7, setSelectedId7] = useState<string | undefined>(undefined);
117114
const [selectedId8, setSelectedId8] = useState<string | undefined>(undefined);
118115
const [selectedId9, setSelectedId9] = useState<string | undefined>(undefined);
@@ -134,9 +131,10 @@ export const TileProviders: FC = () => {
134131
lineHeight: 1.6,
135132
}}
136133
>
137-
The Map component supports 15+ tile provider styles powered by OpenStreetMap data. Choose
138-
from CARTO basemaps, Stamen artistic styles, specialized maps, and more. Simply pass the{' '}
139-
<code>tileProvider</code> prop to any Map component.
134+
The Map component supports 18+ tile provider styles powered by OpenStreetMap data. Choose
135+
from CARTO basemaps, OSM variants, specialized maps, and more. Simply pass the{' '}
136+
<code>tileProvider</code> prop to any Map component. Examples below show free providers that
137+
work without API keys.
140138
</p>
141139

142140
<div
@@ -230,81 +228,6 @@ export const TileProviders: FC = () => {
230228
</div>
231229
</section>
232230

233-
{/* Stamen Design Maps */}
234-
<section style={{ paddingTop: '32px', borderTop: '1px solid var(--ai-color-border-default)' }}>
235-
<h2 style={{ marginBottom: '8px', fontSize: '28px', fontWeight: 600 }}>
236-
Stamen Design Maps
237-
</h2>
238-
<p
239-
style={{
240-
margin: '0 0 32px',
241-
color: 'var(--ai-color-text-secondary)',
242-
lineHeight: 1.6,
243-
}}
244-
>
245-
Distinctive artistic map styles by Stamen Design. Served via Stadia Maps with no API key
246-
required.
247-
</p>
248-
249-
<div style={{ display: 'flex', flexDirection: 'column', gap: '64px' }}>
250-
<ExampleSection
251-
title="Stamen Toner"
252-
description="High-contrast black and white with print-like effect."
253-
code={`<Map
254-
locations={locations}
255-
tileProvider="stamen-toner"
256-
/>`}
257-
>
258-
<CompactMap
259-
key="stamen-toner"
260-
locations={locations}
261-
selectedId={selectedId4}
262-
onLocationSelect={setSelectedId4}
263-
tileProvider="stamen-toner"
264-
defaultCenter={[37.7849, -122.4194]}
265-
defaultZoom={13}
266-
/>
267-
</ExampleSection>
268-
269-
<ExampleSection
270-
title="Stamen Terrain"
271-
description="Topographic terrain style with hillshading. Perfect for outdoor and regional context."
272-
code={`<Map
273-
locations={locations}
274-
tileProvider="stamen-terrain"
275-
/>`}
276-
>
277-
<CompactMap
278-
key="stamen-terrain"
279-
locations={locations}
280-
selectedId={selectedId5}
281-
onLocationSelect={setSelectedId5}
282-
tileProvider="stamen-terrain"
283-
defaultCenter={[37.7849, -122.4194]}
284-
defaultZoom={13}
285-
/>
286-
</ExampleSection>
287-
288-
<ExampleSection
289-
title="Stamen Watercolor"
290-
description="Artistic watercolor rendition of map data."
291-
code={`<Map
292-
locations={locations}
293-
tileProvider="stamen-watercolor"
294-
/>`}
295-
>
296-
<CompactMap
297-
key="stamen-watercolor"
298-
locations={locations}
299-
selectedId={selectedId6}
300-
onLocationSelect={setSelectedId6}
301-
tileProvider="stamen-watercolor"
302-
defaultCenter={[37.7849, -122.4194]}
303-
defaultZoom={13}
304-
/>
305-
</ExampleSection>
306-
</div>
307-
</section>
308231

309232
{/* OpenStreetMap Styles */}
310233
<section style={{ paddingTop: '32px', borderTop: '1px solid var(--ai-color-border-default)' }}>
@@ -460,18 +383,73 @@ export const TileProviders: FC = () => {
460383
lineHeight: 1.6,
461384
}}
462385
>
463-
Additional tile providers are available that require free API keys. All offer generous
464-
free tiers:
386+
Additional tile providers are available that require API keys. All offer generous free
387+
tiers:
465388
</p>
389+
<ul
390+
style={{
391+
margin: '0 0 16px',
392+
paddingLeft: '20px',
393+
color: 'var(--ai-color-text-secondary)',
394+
lineHeight: 1.8,
395+
}}
396+
>
397+
<li>
398+
<strong>Stamen Design Maps</strong> (via Stadia Maps) -{' '}
399+
<a
400+
href="https://client.stadiamaps.com/signup/"
401+
target="_blank"
402+
rel="noopener noreferrer"
403+
style={{ color: 'var(--ai-color-brand-primary)' }}
404+
>
405+
Sign up at Stadia Maps
406+
</a>
407+
<br />
408+
<code>stamen-toner</code>, <code>stamen-terrain</code>, <code>stamen-watercolor</code>
409+
</li>
410+
<li>
411+
<strong>Geoapify</strong> -{' '}
412+
<a
413+
href="https://www.geoapify.com/"
414+
target="_blank"
415+
rel="noopener noreferrer"
416+
style={{ color: 'var(--ai-color-brand-primary)' }}
417+
>
418+
Sign up at Geoapify
419+
</a>
420+
<br />
421+
<code>geoapify-osm-bright-grey</code>, <code>geoapify-osm-bright-smooth</code>
422+
</li>
423+
<li>
424+
<strong>Thunderforest</strong> -{' '}
425+
<a
426+
href="https://www.thunderforest.com/"
427+
target="_blank"
428+
rel="noopener noreferrer"
429+
style={{ color: 'var(--ai-color-brand-primary)' }}
430+
>
431+
Sign up at Thunderforest
432+
</a>
433+
<br />
434+
<code>thunderforest-transport</code>, <code>thunderforest-landscape</code>
435+
</li>
436+
</ul>
466437
<CodeBlock
467-
code={`// Geoapify OSM Bright styles
438+
code={`// Example: Using Stamen Watercolor with API key
439+
<Map
440+
locations={locations}
441+
tileProvider="stamen-watercolor"
442+
tileApiKey={process.env.REACT_APP_STADIA_MAPS_KEY}
443+
/>
444+
445+
// Example: Geoapify OSM Bright
468446
<Map
469447
locations={locations}
470448
tileProvider="geoapify-osm-bright-grey"
471449
tileApiKey={process.env.REACT_APP_GEOAPIFY_KEY}
472450
/>
473451
474-
// Thunderforest Transport
452+
// Example: Thunderforest Transport
475453
<Map
476454
locations={locations}
477455
tileProvider="thunderforest-transport"

packages/ui/src/components/Map/tileProviders.ts

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ export interface TileProviderConfig {
5151
export type TileProviderPreset =
5252
| 'osm-standard'
5353
| 'osm-humanitarian'
54+
| 'osm-france'
55+
| 'osm-bw'
56+
| 'wikimedia'
5457
| 'carto-light'
5558
| 'carto-dark'
5659
| 'carto-voyager'
@@ -96,6 +99,43 @@ export const TILE_PROVIDER_PRESETS: Record<TileProviderPreset, TileProviderConfi
9699
detectRetina: true,
97100
},
98101

102+
/**
103+
* OpenStreetMap France - French-language OSM style
104+
* Free to use, no API key required
105+
*/
106+
'osm-france': {
107+
url: 'https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png',
108+
attribution:
109+
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Tiles by <a href="https://www.openstreetmap.fr">OpenStreetMap France</a>',
110+
subdomains: ['a', 'b', 'c'],
111+
maxZoom: 20,
112+
detectRetina: true,
113+
},
114+
115+
/**
116+
* OpenStreetMap Black & White - Monochrome OSM style
117+
* Free to use, no API key required
118+
*/
119+
'osm-bw': {
120+
url: 'https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png',
121+
attribution:
122+
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
123+
maxZoom: 18,
124+
detectRetina: true,
125+
},
126+
127+
/**
128+
* Wikimedia Maps - Wikimedia Foundation tile service
129+
* Free to use, no API key required
130+
*/
131+
wikimedia: {
132+
url: 'https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png',
133+
attribution:
134+
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="https://foundation.wikimedia.org/wiki/Maps_Terms_of_Use">Wikimedia maps</a>',
135+
maxZoom: 19,
136+
detectRetina: true,
137+
},
138+
99139
/**
100140
* CARTO Light (Positron) - Clean, minimal light theme
101141
* Free to use, no API key required
@@ -138,38 +178,44 @@ export const TILE_PROVIDER_PRESETS: Record<TileProviderPreset, TileProviderConfi
138178

139179
/**
140180
* Stamen Toner - High contrast black & white
141-
* Free to use via Stadia Maps, no API key required
181+
* Requires Stadia Maps API key (free tier available at https://client.stadiamaps.com/signup/)
182+
* Note: These tiles are hosted by Stadia Maps and require authentication
142183
*/
143184
'stamen-toner': {
144-
url: 'https://tiles.stadiamaps.com/tiles/stamen_toner/{z}/{x}/{y}.png',
185+
url: 'https://tiles.stadiamaps.com/tiles/stamen_toner/{z}/{x}/{y}.png?api_key={apiKey}',
145186
attribution:
146187
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://stamen.com">Stamen Design</a>',
147188
maxZoom: 18,
148189
detectRetina: true,
190+
requiresApiKey: true,
149191
},
150192

151193
/**
152194
* Stamen Terrain - Topographic terrain style with hillshading
153-
* Free to use via Stadia Maps, no API key required
195+
* Requires Stadia Maps API key (free tier available at https://client.stadiamaps.com/signup/)
196+
* Note: These tiles are hosted by Stadia Maps and require authentication
154197
*/
155198
'stamen-terrain': {
156-
url: 'https://tiles.stadiamaps.com/tiles/stamen_terrain/{z}/{x}/{y}.png',
199+
url: 'https://tiles.stadiamaps.com/tiles/stamen_terrain/{z}/{x}/{y}.png?api_key={apiKey}',
157200
attribution:
158201
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://stamen.com">Stamen Design</a>',
159202
maxZoom: 18,
160203
detectRetina: true,
204+
requiresApiKey: true,
161205
},
162206

163207
/**
164208
* Stamen Watercolor - Artistic watercolor style
165-
* Free to use via Stadia Maps, no API key required
209+
* Requires Stadia Maps API key (free tier available at https://client.stadiamaps.com/signup/)
210+
* Note: These tiles are hosted by Stadia Maps and require authentication
166211
*/
167212
'stamen-watercolor': {
168-
url: 'https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg',
213+
url: 'https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg?api_key={apiKey}',
169214
attribution:
170215
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://stamen.com">Stamen Design</a>',
171216
maxZoom: 16,
172217
detectRetina: false, // Watercolor doesn't benefit from retina
218+
requiresApiKey: true,
173219
},
174220

175221
/**

0 commit comments

Comments
 (0)