Skip to content

Commit

Permalink
feat(POI Map): add zoom and center options
Browse files Browse the repository at this point in the history
Additional minors changes
- fix(POI Map): update bbox only when its value changes
   Not when a change occurs in the `options` object

- fix(POI Map): remove default bbox
  • Loading branch information
KevinFabre-ods authored Sep 28, 2023
1 parent a123107 commit 7a6eaae
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 27 deletions.
37 changes: 35 additions & 2 deletions packages/visualizations/src/components/MapPoi/Map.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script lang="ts">
import createDeepEqual from '../../stores/createDeepEqual';
import MapRender from './MapRender.svelte';
import type { Async } from '../../types';
Expand All @@ -20,12 +22,43 @@
$: layers = getMapLayers(data.value?.layers);
$: popupsConfiguration = getPopupsConfiguration(data.value?.layers);
$: computedOptions = getMapOptions(options);
$: ({
bbox: _bbox,
zoom,
center: _center,
title,
subtitle,
description,
legend,
sourceLink,
aspectRatio,
interactive,
} = getMapOptions(options));
const bbox = createDeepEqual(_bbox);
const center = createDeepEqual(_center);
$: bbox.update(_bbox);
$: center.update(_center);
</script>

<div>
{#key style}
<MapRender {style} {sources} {layers} {popupsConfiguration} {...computedOptions} />
<MapRender
{style}
{sources}
{layers}
{popupsConfiguration}
bbox={$bbox}
center={$center}
{zoom}
{title}
{subtitle}
{description}
{legend}
{sourceLink}
{aspectRatio}
{interactive}
/>
{/key}
</div>

Expand Down
14 changes: 11 additions & 3 deletions packages/visualizations/src/components/MapPoi/Map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import maplibregl, {
StyleSpecification,
} from 'maplibre-gl';

import { DEFAULT_MAP_CENTER, POPUP_OPTIONS } from './constants';
import type { PopupsConfiguration } from './types';
import { POPUP_OPTIONS } from './constants';
import type { PopupsConfiguration, CenterZoomOptions } from './types';

const CURSOR = {
DEFAULT: 'default',
Expand Down Expand Up @@ -205,7 +205,7 @@ export default class MapPOI {
container: HTMLElement,
options: Omit<MapOptions, 'style' | 'container'>
) {
this.map = new maplibregl.Map({ style, container, center: DEFAULT_MAP_CENTER, ...options });
this.map = new maplibregl.Map({ style, container, ...options });

this.queue((map) => this.initializeMapResizer(map, container));
this.queue((map) => this.initializeCursorBehavior(map));
Expand Down Expand Up @@ -278,6 +278,14 @@ export default class MapPOI {
});
}

/**
* Changes any combination of center and zoom without an animated transition.
* The map will retain its current values for any details not specified in options
*/
jumpTo(options: CenterZoomOptions) {
this.queue((map) => map.jumpTo(options));
}

setPopupsConfiguration(config: PopupsConfiguration) {
this.popupsConfiguration = config;
this.queue((map) => this.setPopup(map));
Expand Down
20 changes: 13 additions & 7 deletions packages/visualizations/src/components/MapPoi/MapRender.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

<script lang="ts">
import type { BBox } from 'geojson';
import type { LngLatBoundsLike, MapOptions, StyleSpecification } from 'maplibre-gl';
import type { LngLatBoundsLike, LngLatLike, MapOptions, StyleSpecification } from 'maplibre-gl';
import { onDestroy, onMount } from 'svelte';
import CategoryLegend from '../Legend/CategoryLegend.svelte';
import type { CategoryLegend as CategoryLegendType } from '../Legend/types';
import SourceLink from '../utils/SourceLink.svelte';
import type { Source } from '../types';
import Map from './Map';
import { getCenterZoomOptions } from './utils';
import type { PopupsConfiguration } from './types';
// Base style, sources and layers
Expand All @@ -18,7 +19,9 @@
export let layers: StyleSpecification['layers'];
// Options
export let bbox: BBox;
export let bbox: BBox | undefined;
export let zoom: number | undefined;
export let center: LngLatLike | undefined;
export let aspectRatio: number;
export let interactive: boolean;
export let title: string | undefined;
Expand All @@ -40,14 +43,17 @@
$: map.setBbox(bbox);
$: map.setSourcesAndLayers(sources, layers);
$: map.setPopupsConfiguration(popupsConfiguration);
$: map.jumpTo(getCenterZoomOptions({ zoom, center }));
$: cssVarStyles = `--aspect-ratio:${aspectRatio};`;
$: if (interactive === false) {
map.setBbox(bbox);
}
// Lifecycle
onMount(() => map.initialize(style, container, { bounds: bbox as LngLatBoundsLike }));
onMount(() => {
const options = {
bounds: bbox as LngLatBoundsLike,
...getCenterZoomOptions({ zoom, center }),
};
map.initialize(style, container, options);
});
onDestroy(() => map.destroy());
</script>

Expand Down
7 changes: 1 addition & 6 deletions packages/visualizations/src/components/MapPoi/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { BBox } from 'geojson';
import type { LngLatLike, PopupOptions, StyleSpecification } from 'maplibre-gl';
import type { PopupOptions, StyleSpecification } from 'maplibre-gl';
import type { Color } from '../types';

export const DEFAULT_BASEMAP_STYLE: StyleSpecification = {
Expand All @@ -9,14 +8,10 @@ export const DEFAULT_BASEMAP_STYLE: StyleSpecification = {
layers: [],
};

export const DEFAULT_BBOX: BBox = [180, 90, -180, -90];

export const DEFAULT_ASPECT_RATIO = 1;

export const DEFAULT_DARK_GREY: Color = '#515457';

export const DEFAULT_MAP_CENTER: LngLatLike = [0, 0];

export const POPUP_OPTIONS: PopupOptions = {
className: 'poi-map__popup',
closeButton: false,
Expand Down
12 changes: 11 additions & 1 deletion packages/visualizations/src/components/MapPoi/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { CircleLayerSpecification, StyleSpecification, GeoJSONFeature } from 'maplibre-gl';
import type {
CircleLayerSpecification,
StyleSpecification,
GeoJSONFeature,
LngLatLike,
} from 'maplibre-gl';
import type { BBox, GeoJsonProperties } from 'geojson';

import type { Color, Source } from '../types';
import type { CategoryLegend } from '../Legend/types';

Expand All @@ -21,6 +27,8 @@ export interface PoiMapOptions {
* Also set the position of the map when rendering.
*/
bbox?: BBox;
center?: LngLatLike;
zoom?: number;
// Aspect ratio used to draw the map. The map will take he width available to it, and decide its height based on that ratio.
aspectRatio?: number;
// Is the map interactive for the user (zoom, move, tooltips...)
Expand Down Expand Up @@ -88,3 +96,5 @@ export type GeoPoint = {
};

export type PopupsConfiguration = { [key: string]: PopupLayer };

export type CenterZoomOptions = { zoom?: number; center?: LngLatLike };
35 changes: 27 additions & 8 deletions packages/visualizations/src/components/MapPoi/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import type {

import type { Color } from '../types';

import type { Layer, PoiMapData, PoiMapOptions, PopupsConfiguration } from './types';
import {
DEFAULT_DARK_GREY,
DEFAULT_BASEMAP_STYLE,
DEFAULT_ASPECT_RATIO,
DEFAULT_BBOX,
} from './constants';
import type {
CenterZoomOptions,
Layer,
PoiMapData,
PoiMapOptions,
PopupsConfiguration,
} from './types';
import { DEFAULT_DARK_GREY, DEFAULT_BASEMAP_STYLE, DEFAULT_ASPECT_RATIO } from './constants';

export const getMapStyle = (style: PoiMapOptions['style']): MapOptions['style'] => {
if (!style) return DEFAULT_BASEMAP_STYLE;
Expand Down Expand Up @@ -98,7 +99,9 @@ export const getPopupsConfiguration = (layers?: Layer[]): PopupsConfiguration =>
export const getMapOptions = (options: PoiMapOptions) => {
const {
aspectRatio = DEFAULT_ASPECT_RATIO,
bbox = DEFAULT_BBOX,
bbox,
zoom,
center,
interactive = true,
title,
subtitle,
Expand All @@ -109,6 +112,8 @@ export const getMapOptions = (options: PoiMapOptions) => {
return {
aspectRatio,
bbox,
zoom,
center,
interactive,
title,
subtitle,
Expand All @@ -117,3 +122,17 @@ export const getMapOptions = (options: PoiMapOptions) => {
sourceLink,
};
};

/**
* Generates a valid CenterZoomOptions object by combining optional zoom and center properties.
*
* @param {CenterZoomOptions} options - An object with optional zoom and center properties.
* @returns A CenterZoomOptions object with valid zoom and center properties is defined.
*/
export const getCenterZoomOptions: (options: CenterZoomOptions) => CenterZoomOptions = ({
zoom,
center,
}) => ({
...(center ? { center } : null),
...(Number.isInteger(zoom) ? { zoom } : null),
});
33 changes: 33 additions & 0 deletions packages/visualizations/src/stores/createDeepEqual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { isEqual } from 'lodash';
// eslint-disable-next-line import/no-extraneous-dependencies
import { writable } from 'svelte/store';

/**
* Creates a Svelte writable store that compares values deeply before updating.
*
* @param initialValue - The initial value for the store.
* @returns An object containing the subscribe and set methods.
*/
const createDeepEqual = <V>(initialValue: V | undefined) => {
const { subscribe, update: internalUpdate } = writable<V | undefined>(initialValue);

return {
/**
* Subscribes to changes in the store's value.
*/
subscribe,
/**
* Update the store value if the new value differs from the store current value
* by performing a deep comparison.
*/
update: (newValue?: V | undefined) => {
internalUpdate((storeValue: V | undefined) => {
// Won't trigger subcribers
if (isEqual(storeValue, newValue)) return storeValue;
return newValue;
});
},
};
};

export default createDeepEqual;

0 comments on commit 7a6eaae

Please sign in to comment.