Skip to content

Commit cf205d5

Browse files
authored
Merge pull request #292 from performant-software/feature/cdc248_georeference_layers
CDC #248 - Geo-reference layers
2 parents 0100a8b + 9f29644 commit cf205d5

File tree

22 files changed

+630
-67
lines changed

22 files changed

+630
-67
lines changed

packages/controlled-vocabulary/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@performant-software/controlled-vocabulary",
3-
"version": "2.2.7",
3+
"version": "2.2.8",
44
"description": "A package of components to allow user to configure dropdown elements. Use with the \"controlled_vocabulary\" gem.",
55
"license": "MIT",
66
"main": "./dist/index.cjs.js",
@@ -23,8 +23,8 @@
2323
"underscore": "^1.13.2"
2424
},
2525
"peerDependencies": {
26-
"@performant-software/semantic-components": "^2.2.7",
27-
"@performant-software/shared-components": "^2.2.7",
26+
"@performant-software/semantic-components": "^2.2.8",
27+
"@performant-software/shared-components": "^2.2.8",
2828
"react": ">= 16.13.1 < 19.0.0",
2929
"react-dom": ">= 16.13.1 < 19.0.0"
3030
},

packages/core-data/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@performant-software/core-data",
3-
"version": "2.2.7",
3+
"version": "2.2.8",
44
"description": "A package of components used with the Core Data platform.",
55
"license": "MIT",
66
"main": "./dist/index.cjs.js",
@@ -37,8 +37,8 @@
3737
"underscore": "^1.13.2"
3838
},
3939
"peerDependencies": {
40-
"@performant-software/shared-components": "^2.2.7",
41-
"@performant-software/geospatial": "^2.2.7",
40+
"@performant-software/shared-components": "^2.2.8",
41+
"@performant-software/geospatial": "^2.2.8",
4242
"@peripleo/maplibre": "^0.5.2",
4343
"@peripleo/peripleo": "^0.5.2",
4444
"react": ">= 16.13.1 < 19.0.0",

packages/core-data/src/components/OverlayLayers.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
11
// @flow
22

3-
import { MapStyles } from '@performant-software/geospatial';
3+
import { MapStyles, WarpedImageLayerPeripleo as WarpedImageLayer } from '@performant-software/geospatial';
44
import { GeoJSONLayer, RasterLayer } from '@peripleo/maplibre';
55
import React from 'react';
66
import _ from 'underscore';
77

88
type Layer = {
99
/**
10-
* The type of layer to render.
10+
* (Optional) GeoJSON data to pass to the layer.
1111
*/
12-
layer_type: 'geojson' | 'raster',
12+
content?: { [key: string]: any },
1313

1414
/**
15-
* (Optional) GeoJSON data to pass to the layer.
15+
* The type of layer to render.
1616
*/
17-
data?: { [key: string]: any },
17+
layer_type: 'geojson' | 'raster' | 'georeference',
1818

1919
/**
2020
* Name of the layer.
2121
*/
2222
name: string,
2323

24+
/**
25+
* (Optional) Layer opacity.
26+
*/
27+
opacity?: number,
28+
2429
/**
2530
* (Optional) URL that contains the layer. This can be a URL to GeoJSON data or a Raster tile set.
2631
*/
@@ -44,7 +49,7 @@ const OverlayLayer = (props: OverlayLayerProps) => {
4449
return (
4550
<GeoJSONLayer
4651
id={overlay.name}
47-
data={overlay.data || overlay.url}
52+
data={overlay.content || overlay.url}
4853
fillStyle={MapStyles.fill}
4954
pointStyle={MapStyles.point}
5055
strokeStyle={MapStyles.stroke}
@@ -61,6 +66,17 @@ const OverlayLayer = (props: OverlayLayerProps) => {
6166
);
6267
}
6368

69+
if (overlay.layer_type === 'georeference') {
70+
return (
71+
<WarpedImageLayer
72+
id={overlay.name}
73+
manifest={overlay.content}
74+
opacity={overlay.opacity}
75+
url={overlay.url}
76+
/>
77+
);
78+
}
79+
6480
return null;
6581
};
6682

packages/core-data/src/components/PlaceLayersSelector.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const PlaceLayersSelector = (props: Props) => {
4747
if (isSelected(layer)) {
4848
setSelectedLayers((prevSelected) => _.filter(prevSelected, (l) => l.url !== layer.url));
4949
} else {
50-
setSelectedLayers((prevSelected) => [...prevSelected, layer]);
50+
setSelectedLayers((prevSelected) => [...prevSelected, { ...layer, content: JSON.parse(layer.content || '{}') }]);
5151
}
5252
}, [isSelected]);
5353

packages/geospatial/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@performant-software/geospatial",
3-
"version": "2.2.7",
3+
"version": "2.2.8",
44
"description": "A package of components for all things map-related.",
55
"license": "MIT",
66
"main": "./dist/index.cjs.js",
@@ -18,6 +18,7 @@
1818
"build": "vite build && flow-copy-source -v src types"
1919
},
2020
"dependencies": {
21+
"@allmaps/maplibre": "^1.0.0-beta.25",
2122
"@mapbox/mapbox-gl-draw": "^1.4.3",
2223
"@maptiler/geocoding-control": "^1.2.2",
2324
"@turf/turf": "^6.5.0",

packages/geospatial/src/components/GeoJsonLayer.js

Lines changed: 25 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,48 +15,33 @@ type Props = {
1515
url?: string
1616
};
1717

18-
const GeoJsonLayer = (props: Props) => {
19-
const [data, setData] = useState(props.data);
20-
21-
/**
22-
* If the data is passed as a URL, fetches the passed URL and sets the response on the state.
23-
*/
24-
useEffect(() => {
25-
if (props.url) {
26-
fetch(props.url)
27-
.then((response) => response.json())
28-
.then((json) => setData(json));
29-
}
30-
}, [props.url]);
31-
32-
return (
33-
<Source
34-
data={data}
35-
type='geojson'
36-
>
37-
<Layer
38-
filter={['!=', '$type', 'Point']}
39-
paint={props.fillStyle}
40-
type='fill'
41-
/>
42-
<Layer
43-
filter={['!=', '$type', 'Point']}
44-
paint={props.lineStyle}
45-
type='line'
46-
/>
47-
<Layer
48-
filter={['==', '$type', 'Point']}
49-
paint={props.pointStyle}
50-
type='circle'
51-
/>
52-
</Source>
53-
);
54-
};
18+
const GeoJsonLayer = (props: Props) => (
19+
<Source
20+
data={props.data || props.url}
21+
type='geojson'
22+
>
23+
<Layer
24+
filter={['!=', '$type', 'Point']}
25+
paint={props.fillStyle}
26+
type='fill'
27+
/>
28+
<Layer
29+
filter={['!=', '$type', 'Point']}
30+
paint={props.lineStyle}
31+
type='line'
32+
/>
33+
<Layer
34+
filter={['==', '$type', 'Point']}
35+
paint={props.pointStyle}
36+
type='circle'
37+
/>
38+
</Source>
39+
);
5540

5641
GeoJsonLayer.defaultProps = {
57-
fillStyle: MapStyles.fill,
58-
pointStyle: MapStyles.point,
59-
strokeStyle: MapStyles.stroke
42+
fillStyle: MapStyles.fill.paint,
43+
pointStyle: MapStyles.point.paint,
44+
strokeStyle: MapStyles.stroke.paint
6045
};
6146

6247
export default GeoJsonLayer;

packages/geospatial/src/components/LayerMenu.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import React, {
44
Children,
55
useCallback,
66
useEffect,
7-
useMemo, useRef,
7+
useMemo,
8+
useRef,
89
useState
910
} from 'react';
1011
import { BsStack } from 'react-icons/bs';
@@ -103,6 +104,7 @@ const LayerMenu = (props: Props) => {
103104
position={props.position}
104105
>
105106
<button
107+
aria-label='Toggle Menu'
106108
className='mapbox-gl-draw_ctrl-draw-btn layer-button'
107109
onClick={() => setMenuOpen((prevMenuOpen) => !prevMenuOpen)}
108110
type='button'

packages/geospatial/src/components/MapDraw.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ type Props = {
6565
*/
6666
mapStyle: string,
6767

68+
/**
69+
* The maximum pitch of the map (0-85).
70+
*/
71+
maxPitch?: number,
72+
6873
/**
6974
* If `true`, the navigation controls will display.
7075
*/
@@ -82,6 +87,11 @@ type Props = {
8287
*/
8388
onGeocodingSelection?: (data: any) => void,
8489

90+
/**
91+
* If `true`, the map's canvas can be exported to a PNG using `map.getCanvas().toDataURL()`.
92+
*/
93+
preserveDrawingBuffer?: boolean,
94+
8595
/**
8696
* Map style object.
8797
*/
@@ -197,8 +207,10 @@ const MapDraw = (props: Props) => {
197207
<Map
198208
attributionControl={false}
199209
cooperativeGestures={props.cooperativeGestures}
200-
onLoad={() => setLoaded(true)}
201210
mapLib={maplibregl}
211+
maxPitch={props.maxPitch}
212+
onLoad={() => setLoaded(true)}
213+
preserveDrawingBuffer={props.preserveDrawingBuffer}
202214
ref={mapRef}
203215
style={style}
204216
mapStyle={mapStyleUrl}
@@ -241,6 +253,7 @@ const MapDraw = (props: Props) => {
241253
MapDraw.defaultProps = {
242254
buffer: DEFAULT_BUFFER,
243255
cooperativeGestures: true,
256+
preserveDrawingBuffer: false,
244257
zoomDuration: DEFAULT_ZOOM_DELAY
245258
};
246259

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// @flow
2+
3+
import { useEffect, useState } from 'react';
4+
import { useMap } from 'react-map-gl';
5+
import MapUtils from '../utils/Map';
6+
7+
type Props = {
8+
/**
9+
* ID of the new layer.
10+
*/
11+
id: string,
12+
13+
/**
14+
* (Optional) IIIF manifest content containing the image and geo-reference annotations.
15+
*/
16+
manifest?: string,
17+
18+
/**
19+
* (Optional) layer opacity.
20+
*/
21+
opacity?: number,
22+
23+
/**
24+
* (Optional) URL to the IIIF manifest.
25+
*/
26+
url?: string
27+
};
28+
29+
const WarpedImageLayer = (props: Props) => {
30+
const [loaded, setLoaded] = useState(false);
31+
32+
const mapRef = useMap();
33+
34+
/**
35+
* Sets the "loaded" attribute on the state based on the current map state.
36+
*/
37+
useEffect(() => {
38+
const instance = mapRef?.current?.getMap();
39+
if (instance && instance.loaded()) {
40+
setLoaded(true);
41+
} else if (instance) {
42+
instance.on('load', () => setLoaded(true));
43+
}
44+
}, []);
45+
46+
/**
47+
* Adds the WarpedMapLayer object to the map as a new layer.
48+
*/
49+
useEffect(() => {
50+
if (!loaded) {
51+
return undefined;
52+
}
53+
54+
const map = mapRef.current.getMap();
55+
56+
MapUtils.addGeoreferenceLayer(map, props.id, {
57+
manifest: props.manifest,
58+
opacity: props.opacity,
59+
url: props.url
60+
});
61+
62+
return () => MapUtils.removeLayer(map, props.id);
63+
}, [loaded]);
64+
65+
return null;
66+
};
67+
68+
WarpedImageLayer.defaultProps = {
69+
opacity: 0.5
70+
};
71+
72+
export default WarpedImageLayer;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// @flow
2+
3+
import { useEffect } from 'react';
4+
import { useLoadedMap } from '@peripleo/maplibre';
5+
import MapUtils from '../utils/Map';
6+
7+
type Props = {
8+
/**
9+
* ID of the new layer.
10+
*/
11+
id: string,
12+
13+
/**
14+
* (Optional) IIIF manifest content containing the image and geo-reference annotations.
15+
*/
16+
manifest?: string,
17+
18+
/**
19+
* (Optional) layer opacity.
20+
*/
21+
opacity?: number,
22+
23+
/**
24+
* (Optional) URL to the IIIF manifest.
25+
*/
26+
url?: string
27+
};
28+
29+
const WarpedImageLayer = (props: Props) => {
30+
const map = useLoadedMap();
31+
32+
/**
33+
* Adds the WarpedMapLayer object to the map as a new layer.
34+
*/
35+
useEffect(() => {
36+
if (!map) {
37+
return undefined;
38+
}
39+
40+
MapUtils.addGeoreferenceLayer(map, props.id, {
41+
manifest: props.manifest,
42+
opacity: props.opacity,
43+
url: props.url
44+
});
45+
46+
return () => MapUtils.removeLayer(map, props.id);
47+
}, [map]);
48+
49+
return null;
50+
};
51+
52+
WarpedImageLayer.defaultProps = {
53+
opacity: 0.5
54+
};
55+
56+
export default WarpedImageLayer;

0 commit comments

Comments
 (0)