Skip to content

Commit

Permalink
feat(Map): support fill layers (#246)
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinFabre-ods authored Jul 8, 2024
1 parent 614a333 commit d0fe950
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 28 deletions.
73 changes: 51 additions & 22 deletions packages/visualizations-react/stories/Poi/PoiMap.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BBox } from 'geojson';
import { CATEGORY_ITEM_VARIANT, POPUP_DISPLAY } from '@opendatasoft/visualizations';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import type {
CategoryItem,
Layer,
PoiMapOptions,
PopupDisplayTypes,
Expand All @@ -16,8 +17,8 @@ import sources from './sources';

const BASE_STYLE = 'https://demotiles.maplibre.org/style.json';

const layer1: Layer = {
id: 'layer-001',
const citiesLayer: Layer = {
id: 'layer-cities',
source: 'cities',
type: 'circle',
color: 'black',
Expand All @@ -33,8 +34,8 @@ const layer1: Layer = {
},
};

const layer2: Layer = {
id: 'layer-002',
const battlesLayer: Layer = {
id: 'layer-battles',
source: 'battles',
type: 'symbol',
iconImageId: 'battle-icon',
Expand All @@ -53,7 +54,16 @@ const layer2: Layer = {
},
};

const layers = [layer1, layer2];
const moselleLayer: Layer = {
id: 'layer-moselle',
source: 'moselle',
type: 'fill',
color: '#6E0F12',
borderColor: 'white',
opacity: 0.3,
};

const layers = [citiesLayer, battlesLayer, moselleLayer];

const citiesColorMatch = {
key: 'key',
Expand All @@ -80,7 +90,7 @@ const battleImageMatch = {

const bbox: BBox = [-6.855469, 41.343825, 11.645508, 51.37178];

const legendCitiesItems = [
const legendCitiesItems: CategoryItem[] = [
{
label: 'Paris',
color: citiesColorMatch.colors.Paris,
Expand All @@ -107,7 +117,7 @@ const legendCitiesItems = [
},
];

const legendbattleItems = [
const legendbattleItems: CategoryItem[] = [
{
variant: CATEGORY_ITEM_VARIANT.Image,
label: 'Battle of Verdun',
Expand All @@ -120,10 +130,19 @@ const legendbattleItems = [
},
];

const legendMoselleItems: CategoryItem[] = [
{
variant: CATEGORY_ITEM_VARIANT.Box,
label: 'Moselle',
color: '#6E0F12',
borderColor: 'white',
},
];

const legend = {
type: 'category' as const,
title: 'French cities and famous battles',
items: [...legendCitiesItems, ...legendbattleItems],
items: [...legendCitiesItems, ...legendbattleItems, ...legendMoselleItems],
align: 'start' as const,
};

Expand Down Expand Up @@ -196,8 +215,8 @@ const PoiMapMatchExpressionArgs = {
data: {
value: {
layers: [
{ ...layer1, colorMatch: citiesColorMatch },
{ ...layer2, iconImageMatch: battleImageMatch },
{ ...citiesLayer, colorMatch: citiesColorMatch },
{ ...battlesLayer, iconImageMatch: battleImageMatch },
],
sources,
},
Expand All @@ -214,8 +233,9 @@ const PoiMapLegendStartArgs = {
data: {
value: {
layers: [
{ ...layer1, colorMatch: citiesColorMatch },
{ ...layer2, iconImageMatch: battleImageMatch },
{ ...citiesLayer, colorMatch: citiesColorMatch },
{ ...battlesLayer, iconImageMatch: battleImageMatch },
moselleLayer,
],
sources,
},
Expand All @@ -232,8 +252,9 @@ const PoiMapLegendCenterArgs = {
data: {
value: {
layers: [
{ ...layer1, colorMatch: citiesColorMatch },
{ ...layer2, iconImageMatch: battleImageMatch },
{ ...citiesLayer, colorMatch: citiesColorMatch },
{ ...battlesLayer, iconImageMatch: battleImageMatch },
moselleLayer,
],
sources,
},
Expand All @@ -250,8 +271,9 @@ const PoiMapMinMaxZoomsArgs = {
data: {
value: {
layers: [
{ ...layer1, colorMatch: citiesColorMatch },
{ ...layer2, iconImageMatch: battleImageMatch },
{ ...citiesLayer, colorMatch: citiesColorMatch },
{ ...battlesLayer, iconImageMatch: battleImageMatch },
moselleLayer,
],
sources,
},
Expand All @@ -273,8 +295,9 @@ const PoiMapCooperativeGesturesArgs = {
data: {
value: {
layers: [
{ ...layer1, colorMatch: citiesColorMatch },
{ ...layer2, iconImageMatch: battleImageMatch },
{ ...citiesLayer, colorMatch: citiesColorMatch },
{ ...battlesLayer, iconImageMatch: battleImageMatch },
moselleLayer,
],
sources,
},
Expand Down Expand Up @@ -319,12 +342,18 @@ const StudioResponsiveUsageTemplate = () => {
value: {
layers: [
{
...layer1,
popup: { ...(layer1.popup as PopupLayer), display: popupDisplay },
...citiesLayer,
popup: {
...(citiesLayer.popup as PopupLayer),
display: popupDisplay,
},
},
{
...layer2,
popup: { ...(layer2.popup as PopupLayer), display: popupDisplay },
...battlesLayer,
popup: {
...(battlesLayer.popup as PopupLayer),
display: popupDisplay,
},
},
],
sources,
Expand Down
4 changes: 4 additions & 0 deletions packages/visualizations-react/stories/Poi/sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ const sources : Required<PoiMapData>["value"]["sources"] = {
]
}

},
moselle: {
type: 'geojson',
data: 'https://france-geojson.gregoiredavid.fr/repo/departements/57-moselle/departement-57-moselle.geojson'
}
};

Expand Down
9 changes: 9 additions & 0 deletions packages/visualizations-react/stories/Table/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default [
readingTime: 5.5,
url: 'https://example.com/lorem-ipsum',
geopoint: [2.357573,48.837904],
geoshape: 'centre-val-de-loire',
},
{
title: 'pellentesque nec blog post',
Expand All @@ -21,6 +22,7 @@ export default [
readingTime: 3.8,
url: 'https://example.com/pellentesque-nec',
geopoint: [2.357573,48.837904],
geoshape: 'bretagne',
},
{
title: 'fusce sit amet blog post',
Expand All @@ -33,6 +35,7 @@ export default [
readingTime: 7.2,
url: 'https://example.com/fusce-sit-amet',
geopoint: [2.357573,48.837904],
geoshape: 'nouvelle-aquitaine',
},
{
title: 'vestibulum nec blog post',
Expand All @@ -44,6 +47,7 @@ export default [
readingTime: 4.5,
url: 'https://example.com/vestibulum-nec',
geopoint: [2.357573,48.837904],
geoshape: 'occitanie',
},
{
title: 'Cras At Blog Post',
Expand All @@ -56,6 +60,7 @@ export default [
readingTime: 6.0,
url: 'https://example.com/cras-at',
geopoint: [2.357573,48.837904],
geoshape: 'provence-alpes-cote-d-azur',
},
{
title: 'Quisque A Blog Post',
Expand All @@ -68,6 +73,7 @@ export default [
readingTime: 4.0,
url: 'https://example.com/quisque-a',
geopoint: [2.357573,48.837904],
geoshape: 'auvergne-rhone-alpes',
},
{
title: 'Ut Vitae Blog Post',
Expand All @@ -80,6 +86,7 @@ export default [
readingTime: 5.0,
url: 'https://example.com/ut-vitae',
geopoint: [2.357573,48.837904],
geoshape: 'bourgogne-franche-comte',
},
{
title: 'Integer Id Blog Post',
Expand All @@ -92,6 +99,7 @@ export default [
readingTime: 4.2,
url: 'https://example.com/integer-id',
geopoint: [2.357573,48.837904],
geoshape: 'grand-est',
},
{
title: 'Undefined row',
Expand All @@ -103,6 +111,7 @@ export default [
readingTime: null,
url: undefined,
geopoint: undefined,
geoshape: undefined,
},
{
title: 'Empty row',
Expand Down
27 changes: 27 additions & 0 deletions packages/visualizations-react/stories/Table/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,33 @@ export const columns: Column[] = [
}])
},
},
{
title: 'Geo shapes',
key: 'geoshape',
dataFormat: 'geo',
options: {
mapOptions: {
style: 'https://demotiles.maplibre.org/style.json',
interactive: false,
bbox: [-6.855469, 41.343825, 11.645508, 51.37178],
zoom: 3,
},
display: (v : unknown) => v as string,
sources: (v: unknown) => ({
'table-stories' : {
type: 'geojson',
data: `https://france-geojson.gregoiredavid.fr/repo/regions/${v}/region-${v}.geojson`
},
}),
layers: () => ([{
id:'table-stories-layer',
source: 'table-stories',
type: "fill",
color: 'black',
borderColor: 'white',
}])
},
},
];

const options: TableOptions = {
Expand Down
15 changes: 13 additions & 2 deletions packages/visualizations/src/components/Map/WebGl/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
CircleLayerSpecification,
FillLayerSpecification,
GeoJSONFeature,
GestureOptions,
LngLatLike,
Expand Down Expand Up @@ -56,7 +57,10 @@ export interface WebGlMapOptions {
export type WebGlMapStyleOption = Partial<Pick<StyleSpecification, 'sources' | 'layers'>>;

// Supported layers
export type LayerSpecification = CircleLayerSpecification | SymbolLayerSpecification;
export type LayerSpecification =
| CircleLayerSpecification
| SymbolLayerSpecification
| FillLayerSpecification;

type BaseLayer = {
id: string;
Expand Down Expand Up @@ -103,7 +107,14 @@ export type SymbolLayer = BaseLayer & {
};
};

export type Layer = CircleLayer | SymbolLayer;
export type FillLayer = BaseLayer & {
type: FillLayerSpecification['type'];
color: Color;
borderColor?: Color;
opacity?: number;
};

export type Layer = CircleLayer | SymbolLayer | FillLayer;

export type GeoPoint = {
lat: number;
Expand Down
26 changes: 22 additions & 4 deletions packages/visualizations/src/components/Map/WebGl/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
StyleSpecification,
ExpressionInputType,
SymbolLayerSpecification,
FilterSpecification,
FillLayerSpecification,
} from 'maplibre-gl';

import { isGroupByForMatchExpression, Color } from '../../types';
Expand All @@ -19,6 +19,7 @@ import type {
PopupConfigurationByLayers,
SymbolLayer,
CenterZoomOptions,
FillLayer,
} from './types';
import { DEFAULT_DARK_GREY, DEFAULT_BASEMAP_STYLE, DEFAULT_SORT_KEY_VALUE } from './constants';

Expand All @@ -34,12 +35,10 @@ export const getMapSources = (sources: WebGlMapData['sources']): StyleSpecificat

const getBaseMapLayerConfiguration = (layer: Layer) => {
const { id, source, sourceLayer } = layer;
const filter: FilterSpecification = ['==', ['geometry-type'], 'Point'];
return {
id,
source,
...(sourceLayer ? { 'source-layer': sourceLayer } : null),
filter,
};
};

Expand Down Expand Up @@ -87,6 +86,7 @@ const getMapCircleLayer = (layer: CircleLayer): CircleLayerSpecification => {
}
return {
...getBaseMapLayerConfiguration(layer),
filter: ['==', ['geometry-type'], 'Point'],
type,
paint: {
'circle-radius': circleRadius,
Expand Down Expand Up @@ -117,6 +117,7 @@ const getMapSymbolLayer = (layer: SymbolLayer): SymbolLayerSpecification => {

return {
...getBaseMapLayerConfiguration(layer),
filter: ['==', ['geometry-type'], 'Point'],
type,
layout: {
'icon-size': 1,
Expand All @@ -126,7 +127,22 @@ const getMapSymbolLayer = (layer: SymbolLayer): SymbolLayerSpecification => {
},
};
};
// Only circle and symbol layers are supported

const getMapFillLayer = (layer: FillLayer): FillLayerSpecification => {
const { type, color, borderColor, opacity } = layer;

return {
...getBaseMapLayerConfiguration(layer),
filter: ['==', ['geometry-type'], 'Polygon'],
type,
paint: {
'fill-color': color,
...(borderColor && { 'fill-outline-color': borderColor }),
...(opacity && { 'fill-opacity': opacity }),
},
};
};
// Circle, symbol and fill layers are supported
export const getMapLayers = (layers?: Layer[]): LayerSpecification[] => {
if (!layers) return [];
return layers.map((layer) => {
Expand All @@ -135,6 +151,8 @@ export const getMapLayers = (layers?: Layer[]): LayerSpecification[] => {
return getMapCircleLayer(layer);
case 'symbol':
return getMapSymbolLayer(layer);
case 'fill':
return getMapFillLayer(layer);
default:
throw new Error(`Unexepected layer type for layer: ${layer}`);
}
Expand Down

2 comments on commit d0fe950

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage for this commit

94.64%

Coverage Report
FileBranchesFuncsLinesUncovered Lines
src
   index.ts100%100%100%
src/client
   error.ts100%100%100%
   index.ts74.03%100%95.31%102–103, 124, 13, 146, 148, 148–149, 15, 15, 151, 162, 169, 169, 17, 17, 171, 176, 179, 182, 184, 52, 82
   types.ts100%100%100%
src/odsql
   clauses.ts71.43%80%90.91%14, 32, 42
   index.ts83.72%95.74%94.19%111, 146, 25, 28, 56–57, 57, 57–58, 68, 78–79

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage for this commit

94.64%

Coverage Report
FileBranchesFuncsLinesUncovered Lines
src
   index.ts100%100%100%
src/client
   error.ts100%100%100%
   index.ts74.03%100%95.31%102–103, 124, 13, 146, 148, 148–149, 15, 15, 151, 162, 169, 169, 17, 17, 171, 176, 179, 182, 184, 52, 82
   types.ts100%100%100%
src/odsql
   clauses.ts71.43%80%90.91%14, 32, 42
   index.ts83.72%95.74%94.19%111, 146, 25, 28, 56–57, 57, 57–58, 68, 78–79

Please sign in to comment.