You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
leaflet-components/src/elements/leaflet-tile-layer-wms.ts

101 lines
3.6 KiB
TypeScript

import { CRS, TileLayer, type WMSOptions, type WMSParams } from 'leaflet';
import { bool, str, type PropDef, type Positional } from '../core/props.ts';
import { tileLayerProps, urlProp, type Sourced } from '../core/shared-props.ts';
import {
WithProps,
type LeafletAddEventListener,
type LeafletElementConstructor,
type LeafletRemoveEventListener,
} from '../core/with-props.ts';
import type { TileLayerEvents } from '../core/event-types.ts';
import type { LeafletCRSChangedEvent } from '../core/register.ts';
// WMS request parameters have no individual setters -- they're merged into the
// query string through setParams().
function param<T>(key: keyof WMSParams): (obj: TileLayer.WMS, value: T) => void {
return (obj, value) => {
obj.setParams({ [key]: value } as unknown as WMSParams);
};
}
// `crs` takes a CRS instance, not a primitive. The attribute only covers the
// 4 CRSes Leaflet ships by name -- a CRS Leaflet doesn't ship comes from a
// nested child announcing itself via leaflet-crs-changed instead (see
// #onCrsChanged), which takes priority over this when present. Constructor-
// only, like the rest of tileLayerProps -- Leaflet has no setter for it.
const NAMED_CRS = {
EPSG3857: CRS.EPSG3857,
EPSG4326: CRS.EPSG4326,
EPSG3395: CRS.EPSG3395,
Simple: CRS.Simple,
};
type CrsName = keyof typeof NAMED_CRS;
const crsProp: PropDef<CRS> = {
default: CRS.EPSG3857,
decode: (raw) => NAMED_CRS[raw as CrsName] ?? CRS.EPSG3857,
encode: (value) =>
value === CRS.EPSG3857
? null
: ((Object.keys(NAMED_CRS) as CrsName[]).find((name) => NAMED_CRS[name] === value) ?? null),
};
const PROPS: {
url: Positional<string, Sourced>;
} & typeof tileLayerProps & {
layers: PropDef<string, TileLayer.WMS>;
styles: PropDef<string, TileLayer.WMS>;
format: PropDef<string, TileLayer.WMS>;
transparent: PropDef<boolean, TileLayer.WMS>;
version: PropDef<string, TileLayer.WMS>;
uppercase: PropDef<boolean>;
crs: PropDef<CRS>;
} = {
url: urlProp,
...tileLayerProps,
layers: str('', { set: param('layers') }),
styles: str('', { set: param('styles') }),
format: str('image/jpeg', { set: param('format') }),
transparent: bool(false, { set: param('transparent') }),
version: str('1.1.1', { set: param('version') }),
uppercase: bool(),
crs: crsProp,
};
const Base: LeafletElementConstructor<TileLayer.WMS, typeof PROPS> = WithProps(PROPS);
export default class LeafletTileLayerWMSElement extends Base {
declare readonly leafletObject?: TileLayer.WMS;
declare addEventListener: LeafletAddEventListener<TileLayerEvents>;
declare removeEventListener: LeafletRemoveEventListener<TileLayerEvents>;
// Set by a nested CRS-providing child; overrides the `crs` attribute's
// named lookup when present.
#childCrs?: CRS;
createLeafletObject(options: WMSOptions): TileLayer.WMS {
return new TileLayer.WMS(
this.url,
this.#childCrs ? { ...options, crs: this.#childCrs } : options,
);
}
connectedCallback(): void {
super.connectedCallback();
this.addEventListener('leaflet-crs-changed', this.#onCrsChanged);
}
disconnectedCallback(): void {
this.removeEventListener('leaflet-crs-changed', this.#onCrsChanged);
super.disconnectedCallback();
}
// A CRS Leaflet doesn't ship comes from any custom element nested here
// that fires this event -- no base class required, just this event shape
// (see register.ts). `crs` is constructor-only, so picking it up means
// rebuilding the whole layer.
#onCrsChanged = (e: LeafletCRSChangedEvent) => {
this.#childCrs = e.detail.crs ?? undefined;
this.recreateLeafletObject();
};
}