Add TileLayer Presets configuration

This commit is contained in:
William Lima 2024-06-18 22:01:59 -01:00
parent 31406d48e3
commit 134f72948c
4 changed files with 1338 additions and 33 deletions

View File

@ -5,7 +5,12 @@ import type { Preview } from '@storybook/react';
window.process = { window.process = {
...window.process, ...window.process,
env:{ env:{
...window.process?.env ...window.process?.env,
NEXT_PUBLIC_MAP_TILE_LAYER_NAME:'MapBox',
NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_accessToken: 'pk.eyJ1Ijoid2lsbHktcGFsbWFyZWpvIiwiYSI6ImNqNzk5NmRpNDFzb2cyeG9sc2luMHNjajUifQ.lkoVRFSI8hOLH4uJeOzwXw',
} }
}; };

View File

@ -8,14 +8,36 @@ import {
TileLayer, TileLayer,
GeoJSON as GeoJSONLayer, GeoJSON as GeoJSONLayer,
LayersControl, LayersControl,
TileLayerProps,
} from 'react-leaflet'; } from 'react-leaflet';
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
import * as L from 'leaflet'; import * as L from 'leaflet';
import providers from '../lib/tileLayerPresets';
type VariantKeys<T> = T extends { variants: infer V }
? {
[K in keyof V]: K extends string
? `${K}` | `${K}.${VariantKeys<V[K]>}`
: never;
}[keyof V]
: never;
type ProviderVariantKeys<T> = {
[K in keyof T]: K extends string
? `${K}` | `${K}.${VariantKeys<T[K]>}`
: never;
}[keyof T];
type TileLayerPreset = ProviderVariantKeys<typeof providers> | 'custom';
interface TileLayerSettings extends L.TileLayerOptions {
url?: string;
variant?: string | any;
}
export type MapProps = { export type MapProps = {
tile?: TileLayerProps; tileLayerName: TileLayerPreset;
tileLayerOptions?: TileLayerSettings | undefined;
layers: { layers: {
data: GeospatialData; data: GeospatialData;
name: string; name: string;
@ -38,8 +60,19 @@ export type MapProps = {
}; };
}; };
const tileLayerDefaultName = process?.env
.NEXT_PUBLIC_MAP_TILE_LAYER_NAME as TileLayerPreset;
const tileLayerDefaultOptions = Object.keys(process?.env)
.filter((key) => key.startsWith('NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_'))
.reduce((obj, key) => {
obj[key.split('NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_')[1]] = process.env[key];
return obj;
}, {}) as TileLayerSettings;
export function Map({ export function Map({
tile = undefined, tileLayerName = tileLayerDefaultName || 'OpenStreetMap',
tileLayerOptions,
layers = [ layers = [
{ {
data: null, data: null,
@ -58,36 +91,94 @@ export function Map({
const [layersData, setLayersData] = useState<any>([]); const [layersData, setLayersData] = useState<any>([]);
/* /*
tileEnvVars tileLayerDefaultOptions
extract all environment variables thats starts with NEXT_PUBLIC_MAP_TILE_PROVIDER_. extract all environment variables thats starts with NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_.
the variables names are the same as the TileLayer object properties: the variables names are the same as the TileLayer object properties:
- url: - NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_url:
- attribution - NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_attribution
- accessToken - NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_accessToken
- id - NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_id
- ext - NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_ext
- bounds - NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_bounds
- maxZoom - NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_maxZoom
- minZoom - NEXT_PUBLIC_MAP_TILE_LAYER_OPTION_minZoom
see TileLayerOptions inteface see TileLayerOptions inteface
*/ */
const tileEnvVars = Object.keys(process?.env)
.filter((key) => key.startsWith('NEXT_PUBLIC_MAP_TILE_PROVIDER_'))
.reduce((obj, key) => {
obj[key.split('NEXT_PUBLIC_MAP_TILE_PROVIDER_')[1]] = process.env[key];
return obj;
}, {});
//tileData prioritizes properties passed through component over those passed through .env variables //tileLayerData prioritizes properties passed through component over those passed through .env variables
let tileData = Object.assign(tileEnvVars, tile); tileLayerOptions = Object.assign(tileLayerDefaultOptions, tileLayerOptions);
tileData = tileData.url let provider = {
? tileData url: tileLayerOptions.url,
: { options: tileLayerOptions,
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', };
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', if (tileLayerName != 'custom') {
var parts = tileLayerName.split('.');
var providerName = parts[0];
var variantName: string = parts[1];
//make sure to declare a variant if url depends on a variant: assume first
if (providers[providerName].url?.includes('{variant}') && !variantName)
variantName = Object.keys(providers[providerName].variants)[0];
console.log(variantName);
if (!providers[providerName]) {
throw 'No such provider (' + providerName + ')';
}
provider = {
url: providers[providerName].url,
options: providers[providerName].options,
};
// overwrite values in provider from variant.
if (variantName && 'variants' in providers[providerName]) {
if (!(variantName in providers[providerName].variants)) {
throw 'No such variant of ' + providerName + ' (' + variantName + ')';
}
var variant = providers[providerName].variants[variantName];
var variantOptions;
if (typeof variant === 'string') {
variantOptions = {
variant: variant,
};
} else {
variantOptions = variant.options;
}
provider = {
url: variant.url || provider.url,
options: L.Util.extend({}, provider.options, variantOptions),
}; };
}
var attributionReplacer = function (attr) {
if (attr.indexOf('{attribution.') === -1) {
return attr;
}
return attr.replace(
/\{attribution.(\w*)\}/g,
function (match, attributionName) {
return attributionReplacer(
providers[attributionName].options.attribution
);
}
);
};
provider.options.attribution = attributionReplacer(
provider.options.attribution
);
}
var tileLayerData = L.Util.extend(
{
url: provider.url,
},
provider.options,
tileLayerOptions
);
useEffect(() => { useEffect(() => {
const loadDataPromises = layers.map(async (layer) => { const loadDataPromises = layers.map(async (layer) => {
@ -179,7 +270,7 @@ export function Map({
map.target.fitBounds(layerToZoomBounds); map.target.fitBounds(layerToZoomBounds);
}} }}
> >
{tileData.url && <TileLayer {...tileData} />} {tileLayerData.url && <TileLayer {...tileLayerData} />}
<LayersControl position="bottomright"> <LayersControl position="bottomright">
{layers.map((layer) => { {layers.map((layer) => {

File diff suppressed because it is too large Load Diff

View File

@ -43,11 +43,9 @@ type Story = StoryObj<MapProps>;
export const GeoJSONPolygons: Story = { export const GeoJSONPolygons: Story = {
name: 'GeoJSON polygons map', name: 'GeoJSON polygons map',
args: { args: {
tile : { tileLayerName:'OpenStreetMap',
url : 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', tileLayerOptions:{
attribution:'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://www.mapbox.com/about/maps/">Mapbox</a>',
accessToken : 'pk.eyJ1Ijoid2lsbHktcGFsbWFyZWpvIiwiYSI6ImNqNzk5NmRpNDFzb2cyeG9sc2luMHNjajUifQ.lkoVRFSI8hOLH4uJeOzwXw', accessToken : 'pk.eyJ1Ijoid2lsbHktcGFsbWFyZWpvIiwiYSI6ImNqNzk5NmRpNDFzb2cyeG9sc2luMHNjajUifQ.lkoVRFSI8hOLH4uJeOzwXw',
id: 'mapbox/streets-v10'
}, },
layers: [ layers: [
{ {