Skip to content

Commit fbb9c49

Browse files
authored
feat: add globe view (#282)
Closes #145
1 parent c760c69 commit fbb9c49

File tree

6 files changed

+80
-2
lines changed

6 files changed

+80
-2
lines changed

src/components/header.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { uploadFile } from "../utils/upload";
66
import { Examples } from "./examples";
77
import HrefInput from "./href-input";
88
import { ColorModeButton } from "./ui/color-mode";
9+
import { ProjectionButton } from "./ui/projection";
910

1011
export default function Header() {
1112
const setUploadedFile = useStore((store) => store.setUploadedFile);
@@ -36,6 +37,7 @@ export default function Header() {
3637
Examples
3738
</Button>
3839
</Examples>
40+
<ProjectionButton variant={"surface"} />
3941
<ColorModeButton variant={"surface"} />
4042
</HStack>
4143
);

src/components/map.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ export default function Map() {
3232
"positron-gl-style",
3333
"dark-matter-gl-style"
3434
);
35+
const projection = useStore((store) => store.projection);
3536
const value = useStore((store) => store.value);
3637
const collections = useStore((store) => store.collections);
3738
const setBbox = useStore((store) => store.setBbox);
3839
const cogHref = useStore((store) => store.cogHref);
40+
const cogSources = useStore((store) => store.cogSources);
3941
const pagedCogSources = useStore((store) => store.pagedCogSources);
4042
const hoveredItem = useStore((store) => store.hoveredItem);
4143
const pickedItem = useStore((store) => store.pickedItem);
@@ -185,15 +187,15 @@ export default function Map() {
185187
})
186188
);
187189

188-
if (cogHref)
190+
if (cogHref && projection === "mercator")
189191
layers.push(
190192
new COGLayer({
191193
id: "cog-" + cogHref,
192194
geotiff: cogHref,
193195
geoKeysParser,
194196
})
195197
);
196-
else if (visualizeItems && pagedCogSources)
198+
else if (visualizeItems && pagedCogSources && projection === "mercator")
197199
pagedCogSources.forEach((page, i) => {
198200
if (page)
199201
layers.push(
@@ -214,6 +216,24 @@ export default function Map() {
214216
})
215217
);
216218
});
219+
else if (visualizeItems && cogSources && projection === "mercator")
220+
layers.push(
221+
new MosaicLayer({
222+
id: "cog-mosaic",
223+
sources: cogSources,
224+
getSource: async (source) => {
225+
return source.assets.data.href;
226+
},
227+
renderSource: (source, { data, signal }) => {
228+
return new COGLayer({
229+
id: `cog-${source.id}`,
230+
geotiff: data,
231+
geoKeysParser,
232+
signal,
233+
});
234+
},
235+
})
236+
);
217237

218238
return (
219239
<MaplibreMap
@@ -224,6 +244,7 @@ export default function Map() {
224244
latitude: 0,
225245
zoom: 1,
226246
}}
247+
projection={projection}
227248
mapStyle={`https://basemaps.cartocdn.com/gl/${mapStyle}/style.json`}
228249
style={{ zIndex: 0 }}
229250
onLoad={() => setIsLoaded(true)}

src/components/ui/projection.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { IconButtonProps } from "@chakra-ui/react";
2+
import { IconButton } from "@chakra-ui/react";
3+
import * as React from "react";
4+
import { LuGlobe, LuMap } from "react-icons/lu";
5+
import { useStore } from "../../store";
6+
7+
function ProjectionIcon() {
8+
const projection = useStore((store) => store.projection);
9+
return projection === "globe" ? <LuGlobe /> : <LuMap />;
10+
}
11+
12+
interface ProjectionButtonProps extends Omit<IconButtonProps, "aria-label"> {}
13+
14+
export const ProjectionButton = React.forwardRef<
15+
HTMLButtonElement,
16+
ProjectionButtonProps
17+
>(function ProjectionButton(props, ref) {
18+
const toggleProjection = useStore((store) => store.toggleProjection);
19+
return (
20+
<IconButton
21+
onClick={toggleProjection}
22+
variant="ghost"
23+
aria-label="Toggle projection"
24+
ref={ref}
25+
{...props}
26+
>
27+
<ProjectionIcon />
28+
</IconButton>
29+
);
30+
});

src/components/value/items.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export default function Items({ items }: { items: StacItem[] }) {
3737
const searchedItems = useStore((store) => store.searchedItems);
3838
const itemSource = useStore((store) => store.itemSource);
3939
const setItemSource = useStore((store) => store.setItemSource);
40+
const projection = useStore((store) => store.projection);
4041

4142
const hasStatic = staticItems && staticItems.length > 0;
4243
const hasSearched = searchedItems && searchedItems.length > 0;
@@ -141,6 +142,7 @@ export default function Items({ items }: { items: StacItem[] }) {
141142
onClick={() => setVisualizeItems(!visualizeItems)}
142143
size={"xs"}
143144
variant={"ghost"}
145+
disabled={projection === "globe"}
144146
>
145147
{visualizeItems ? <LuEye /> : <LuEyeClosed />}
146148
{visualizeItems

src/store/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { createDatetimeSlice, type DatetimeState } from "./datetime";
99
import { createHrefSlice, type HrefState } from "./href";
1010
import { createInputSlice, type InputState } from "./input";
1111
import { createItemsSlice, type ItemsState } from "./items";
12+
import { createMapSlice, type MapState } from "./map";
1213
import {
1314
createStacGeoparquetState,
1415
type StacGeoparquetState,
@@ -33,6 +34,7 @@ export interface State
3334
UploadedFileState,
3435
ConnectionState,
3536
DatetimeState,
37+
MapState,
3638
StacGeoparquetState {
3739
fillColor: [number, number, number, number];
3840
lineColor: [number, number, number, number];
@@ -53,6 +55,7 @@ export const useStore = create<State>((...a) => ({
5355
...createStacGeoparquetState(...a),
5456
...createCatalogsSlice(...a),
5557
...createDatetimeSlice(...a),
58+
...createMapSlice(...a),
5659
fillColor: [207, 63, 2, 50] as [number, number, number, number],
5760
lineColor: [207, 63, 2, 100] as [number, number, number, number],
5861
lineWidth: 2,

src/store/map.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { StateCreator } from "zustand";
2+
import type { State } from ".";
3+
4+
export type Projection = "mercator" | "globe";
5+
6+
export interface MapState {
7+
projection: Projection;
8+
setProjection: (projection: Projection) => void;
9+
toggleProjection: () => void;
10+
}
11+
12+
export const createMapSlice: StateCreator<State, [], [], MapState> = (
13+
set,
14+
get
15+
) => ({
16+
projection: "mercator",
17+
setProjection: (projection) => set({ projection }),
18+
toggleProjection: () =>
19+
set({ projection: get().projection === "mercator" ? "globe" : "mercator" }),
20+
});

0 commit comments

Comments
 (0)