@macrostrat/map-interface 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +1103 -0
- package/dist/index.css.map +1 -0
- package/dist/index.js +5162 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
- package/src/container.ts +140 -0
- package/src/context-panel/index.ts +53 -0
- package/src/context-panel/main.module.sass +37 -0
- package/src/controls.ts +52 -0
- package/src/dev/index.ts +179 -0
- package/src/dev/main.module.sass +63 -0
- package/src/dev/tile-extent.ts +46 -0
- package/src/dev/vector-tile-features.ts +208 -0
- package/src/dev/xray.ts +118 -0
- package/src/expansion-panel/headers.ts +18 -0
- package/src/expansion-panel/index.ts +134 -0
- package/src/expansion-panel/main.module.sass +143 -0
- package/src/helpers.ts +171 -0
- package/src/index.ts +5 -0
- package/src/location-info/index.ts +89 -0
- package/src/location-info/utils.ts +44 -0
- package/src/location-panel/header.ts +86 -0
- package/src/location-panel/index.ts +22 -0
- package/src/location-panel/main.module.sass +53 -0
- package/src/main.module.ref.styl +407 -0
- package/src/main.module.sass +419 -0
- package/src/map-view/index.ts +163 -0
- package/src/map-view/main.module.sass +13 -0
- package/src/map-view/terrain.ts +60 -0
- package/src/utils.ts +33 -0
- package/stories/coordinates.stories.ts +39 -0
- package/stories/dev-map-page.stories.ts +26 -0
- package/stories/map-interface.stories.ts +27 -0
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@macrostrat/map-interface",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Map interface for Macrostrat",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"source": "src/index.ts",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"module": "dist/index.mjs",
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@macrostrat/hyper": "^2.2.1",
|
|
11
|
+
"@macrostrat/mapbox-react": "2.0.0",
|
|
12
|
+
"@macrostrat/mapbox-utils": "1.1.1",
|
|
13
|
+
"@mapbox/tilebelt": "^1.0.2",
|
|
14
|
+
"@turf/bbox": "^6.5.0",
|
|
15
|
+
"chroma-js": "^2.4.2",
|
|
16
|
+
"classnames": "^2.3.2",
|
|
17
|
+
"d3-array": "^3.2.4",
|
|
18
|
+
"d3-format": "^3.1.0",
|
|
19
|
+
"mapbox-gl": "^2.11.0",
|
|
20
|
+
"transition-hook": "^1.5.2",
|
|
21
|
+
"underscore": "^1.13.6",
|
|
22
|
+
"use-resize-observer": "^9.1.0"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@blueprintjs/core": "^3.43.0 || ^4.3.0",
|
|
26
|
+
"react": "^16.8.6||^17.0.0||^18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"dev": "parcel watch",
|
|
30
|
+
"build": "parcel build"
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
"typescript": "./src/index.ts"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/container.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import hyper from "@macrostrat/hyper";
|
|
2
|
+
import { HTMLDivProps } from "@blueprintjs/core";
|
|
3
|
+
import styles from "./main.module.sass";
|
|
4
|
+
import classNames from "classnames";
|
|
5
|
+
import { useTransition } from "transition-hook";
|
|
6
|
+
import {
|
|
7
|
+
MapboxMapProvider,
|
|
8
|
+
ZoomControl,
|
|
9
|
+
useMapStatus,
|
|
10
|
+
} from "@macrostrat/mapbox-react";
|
|
11
|
+
import { ToasterContext } from "@macrostrat/ui-components";
|
|
12
|
+
import { MapBottomControls } from "./controls";
|
|
13
|
+
import { mapViewInfo, MapPosition } from "@macrostrat/mapbox-utils";
|
|
14
|
+
import { Card } from "@blueprintjs/core";
|
|
15
|
+
|
|
16
|
+
import { ReactNode } from "react";
|
|
17
|
+
|
|
18
|
+
export * from "./location-panel";
|
|
19
|
+
export * from "./context-panel";
|
|
20
|
+
//export * from "./dev";
|
|
21
|
+
|
|
22
|
+
export function MapInterface() {
|
|
23
|
+
return h("div", "Hello world");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const h = hyper.styled(styles);
|
|
27
|
+
|
|
28
|
+
type AnyElement = React.ReactNode | React.ReactElement | React.ReactFragment;
|
|
29
|
+
|
|
30
|
+
export const PanelCard = (props) =>
|
|
31
|
+
h(Card, { ...props, className: classNames("panel-card", props.className) });
|
|
32
|
+
|
|
33
|
+
function _MapAreaContainer({
|
|
34
|
+
children,
|
|
35
|
+
className,
|
|
36
|
+
navbar,
|
|
37
|
+
contextPanel = null,
|
|
38
|
+
detailPanel = null,
|
|
39
|
+
detailPanelOpen,
|
|
40
|
+
contextPanelOpen = true,
|
|
41
|
+
bottomPanel = null,
|
|
42
|
+
mainPanel,
|
|
43
|
+
mapControls = h(MapBottomControls),
|
|
44
|
+
contextStackProps = null,
|
|
45
|
+
detailStackProps = null,
|
|
46
|
+
mapPosition = null,
|
|
47
|
+
fitViewport = true,
|
|
48
|
+
...rest
|
|
49
|
+
}: {
|
|
50
|
+
navbar: AnyElement;
|
|
51
|
+
children?: AnyElement;
|
|
52
|
+
mapControls?: AnyElement;
|
|
53
|
+
contextPanel?: AnyElement;
|
|
54
|
+
mainPanel?: AnyElement;
|
|
55
|
+
detailPanel?: AnyElement;
|
|
56
|
+
bottomPanel?: AnyElement;
|
|
57
|
+
className?: string;
|
|
58
|
+
detailPanelOpen?: boolean;
|
|
59
|
+
contextPanelOpen?: boolean;
|
|
60
|
+
contextStackProps?: HTMLDivProps;
|
|
61
|
+
detailStackProps?: HTMLDivProps;
|
|
62
|
+
mapPosition?: MapPosition;
|
|
63
|
+
fitViewport?: boolean;
|
|
64
|
+
}) {
|
|
65
|
+
const _detailPanelOpen = detailPanelOpen ?? detailPanel != null;
|
|
66
|
+
const contextPanelTrans = useTransition(contextPanelOpen, 800);
|
|
67
|
+
const detailPanelTrans = useTransition(_detailPanelOpen, 800);
|
|
68
|
+
|
|
69
|
+
/*- We apply a custom style to the panel container when we are interacting
|
|
70
|
+
with the search bar, so that we can block map interactions until search
|
|
71
|
+
bar focus is lost.
|
|
72
|
+
- We also apply a custom style when the infodrawer is open so we can hide
|
|
73
|
+
the search bar on mobile platforms
|
|
74
|
+
- These styles are doubly applied so we can have both namespaced and
|
|
75
|
+
outside-accessible styles for each case.
|
|
76
|
+
*/
|
|
77
|
+
const _className = classNames(
|
|
78
|
+
{
|
|
79
|
+
searching: false,
|
|
80
|
+
"detail-panel-open": _detailPanelOpen,
|
|
81
|
+
"map-context-open": _detailPanelOpen,
|
|
82
|
+
"fit-viewport": fitViewport,
|
|
83
|
+
},
|
|
84
|
+
`context-panel-${contextPanelTrans.stage}`,
|
|
85
|
+
`map-context-${contextPanelTrans.stage}`,
|
|
86
|
+
`detail-panel-${detailPanelTrans.stage}`,
|
|
87
|
+
`map-detail-${detailPanelTrans.stage}`
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return h(
|
|
91
|
+
MapStyledContainer,
|
|
92
|
+
{ className: classNames("map-page", className), mapPosition },
|
|
93
|
+
[
|
|
94
|
+
h("div.main-ui", { className: _className, ...rest }, [
|
|
95
|
+
h("div.context-stack", contextStackProps, [
|
|
96
|
+
navbar,
|
|
97
|
+
h.if(contextPanelTrans.shouldMount)([contextPanel]),
|
|
98
|
+
]),
|
|
99
|
+
//h(MapView),
|
|
100
|
+
children ?? mainPanel,
|
|
101
|
+
h("div.detail-stack.infodrawer-container", detailStackProps, [
|
|
102
|
+
detailPanel,
|
|
103
|
+
h(ZoomControl, { className: "zoom-control" }),
|
|
104
|
+
h("div.spacer"),
|
|
105
|
+
mapControls,
|
|
106
|
+
]),
|
|
107
|
+
]),
|
|
108
|
+
h("div.bottom", null, bottomPanel),
|
|
109
|
+
]
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const MapProviders = ({ children }) =>
|
|
114
|
+
h(ToasterContext, h(MapboxMapProvider, children));
|
|
115
|
+
|
|
116
|
+
export const MapAreaContainer = (props) =>
|
|
117
|
+
h(MapProviders, h(_MapAreaContainer, props));
|
|
118
|
+
|
|
119
|
+
interface MapContainerProps {
|
|
120
|
+
className?: string;
|
|
121
|
+
mapPosition?: MapPosition;
|
|
122
|
+
children?: ReactNode;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function MapStyledContainer({ className, children }: MapContainerProps) {
|
|
126
|
+
const { mapPosition } = useMapStatus();
|
|
127
|
+
if (mapPosition != null) {
|
|
128
|
+
const { mapIsRotated, mapUse3D, mapIsGlobal } = mapViewInfo(mapPosition);
|
|
129
|
+
className = classNames(className, {
|
|
130
|
+
"map-is-rotated": mapIsRotated,
|
|
131
|
+
"map-3d-available": mapUse3D,
|
|
132
|
+
"map-is-global": mapIsGlobal,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return h("div", { className }, children);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export * from "./map-view";
|
|
140
|
+
//const _MapPage = compose(HotkeysProvider, MapPage);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { Navbar, Button, InputGroup, Spinner, Card } from "@blueprintjs/core";
|
|
3
|
+
import hyper from "@macrostrat/hyper";
|
|
4
|
+
import styles from "./main.module.sass";
|
|
5
|
+
import { useMapStatus } from "@macrostrat/mapbox-react";
|
|
6
|
+
|
|
7
|
+
const h = hyper.styled(styles);
|
|
8
|
+
|
|
9
|
+
const spinnerElement = h(Spinner, { size: 16 });
|
|
10
|
+
|
|
11
|
+
export function LoadingButton({
|
|
12
|
+
isLoading = false,
|
|
13
|
+
onClick,
|
|
14
|
+
active = false,
|
|
15
|
+
icon = "menu",
|
|
16
|
+
}) {
|
|
17
|
+
return h(Button, {
|
|
18
|
+
icon: isLoading ? spinnerElement : icon,
|
|
19
|
+
large: true,
|
|
20
|
+
minimal: true,
|
|
21
|
+
onClick,
|
|
22
|
+
active: active && !isLoading,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function MapLoadingButton(props) {
|
|
27
|
+
const { isLoading } = useMapStatus();
|
|
28
|
+
const mapIsLoading = useMemo(() => isLoading, [isLoading]);
|
|
29
|
+
return h(LoadingButton, { ...props, isLoading: mapIsLoading });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type AnyChildren = React.ReactNode | React.ReactFragment;
|
|
33
|
+
|
|
34
|
+
export function FloatingNavbar({
|
|
35
|
+
className,
|
|
36
|
+
children,
|
|
37
|
+
statusElement = null,
|
|
38
|
+
}: {
|
|
39
|
+
className?: string;
|
|
40
|
+
children?: AnyChildren;
|
|
41
|
+
statusElement?: AnyChildren;
|
|
42
|
+
}) {
|
|
43
|
+
return h("div.searchbar-holder", { className }, [
|
|
44
|
+
h("div.navbar-holder", [
|
|
45
|
+
h(Navbar, { className: "searchbar panel" }, children),
|
|
46
|
+
]),
|
|
47
|
+
h.if(statusElement != null)(
|
|
48
|
+
Card,
|
|
49
|
+
{ className: "status-tongue" },
|
|
50
|
+
statusElement
|
|
51
|
+
),
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
.searchbar-holder
|
|
2
|
+
transition: margin 300ms
|
|
3
|
+
display: flex
|
|
4
|
+
flex-direction: column
|
|
5
|
+
margin: 0
|
|
6
|
+
.navbar-holder
|
|
7
|
+
display: flex
|
|
8
|
+
flex-direction: row
|
|
9
|
+
.searchbar
|
|
10
|
+
width: 100%
|
|
11
|
+
background-color: var(--panel-background-color)
|
|
12
|
+
border-radius: 3px
|
|
13
|
+
padding: 0 5px
|
|
14
|
+
display: flex
|
|
15
|
+
flex-direction: row
|
|
16
|
+
align-items: center
|
|
17
|
+
:global(.bp4-input-group)
|
|
18
|
+
flex-grow: 1
|
|
19
|
+
cursor: text
|
|
20
|
+
&>*
|
|
21
|
+
margin-right: 5px
|
|
22
|
+
&:last-child
|
|
23
|
+
margin-right: 0
|
|
24
|
+
|
|
25
|
+
.status-tongue
|
|
26
|
+
background-color: var(--panel-background-color)
|
|
27
|
+
margin: 5px
|
|
28
|
+
margin-top: -12px
|
|
29
|
+
padding: 0
|
|
30
|
+
padding-top: 12px
|
|
31
|
+
|
|
32
|
+
@media screen and (max-width: 768px)
|
|
33
|
+
.status-tongue
|
|
34
|
+
max-width: 100vw
|
|
35
|
+
margin: 0
|
|
36
|
+
border-radius: 0
|
|
37
|
+
margin-top: -3px
|
package/src/controls.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
import { GeolocateControl } from "mapbox-gl";
|
|
3
|
+
import hyper from "@macrostrat/hyper";
|
|
4
|
+
import styles from "./main.module.sass";
|
|
5
|
+
import {
|
|
6
|
+
CompassControl,
|
|
7
|
+
GlobeControl,
|
|
8
|
+
ThreeDControl,
|
|
9
|
+
MapControlWrapper,
|
|
10
|
+
} from "@macrostrat/mapbox-react";
|
|
11
|
+
import { ScaleControl as BaseScaleControl } from "mapbox-gl";
|
|
12
|
+
|
|
13
|
+
const h = hyper.styled(styles);
|
|
14
|
+
|
|
15
|
+
function ScaleControl(props) {
|
|
16
|
+
const optionsRef = useRef({
|
|
17
|
+
maxWidth: 200,
|
|
18
|
+
unit: "metric",
|
|
19
|
+
});
|
|
20
|
+
return h(MapControlWrapper, {
|
|
21
|
+
className: "map-scale-control",
|
|
22
|
+
control: BaseScaleControl,
|
|
23
|
+
options: optionsRef.current,
|
|
24
|
+
...props,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function GeolocationControl(props) {
|
|
29
|
+
const optionsRef = useRef({
|
|
30
|
+
showAccuracyCircle: true,
|
|
31
|
+
showUserLocation: true,
|
|
32
|
+
trackUserLocation: true,
|
|
33
|
+
positionOptions: {
|
|
34
|
+
enableHighAccuracy: true,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
return h(MapControlWrapper, {
|
|
38
|
+
control: GeolocateControl,
|
|
39
|
+
options: optionsRef.current,
|
|
40
|
+
...props,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function MapBottomControls() {
|
|
45
|
+
return h("div.map-controls", [
|
|
46
|
+
h(ScaleControl),
|
|
47
|
+
h(ThreeDControl, { className: "map-3d-control" }),
|
|
48
|
+
h(CompassControl, { className: "compass-control" }),
|
|
49
|
+
h(GlobeControl, { className: "globe-control" }),
|
|
50
|
+
h(GeolocationControl, { className: "geolocation-control" }),
|
|
51
|
+
]);
|
|
52
|
+
}
|
package/src/dev/index.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// Import other components
|
|
2
|
+
import { Switch } from "@blueprintjs/core";
|
|
3
|
+
import hyper from "@macrostrat/hyper";
|
|
4
|
+
import { Spacer, useDarkMode, useStoredState } from "@macrostrat/ui-components";
|
|
5
|
+
import mapboxgl from "mapbox-gl";
|
|
6
|
+
import { useCallback, useState, useEffect } from "react";
|
|
7
|
+
import { buildInspectorStyle, buildXRayStyle } from "./xray";
|
|
8
|
+
import { MapAreaContainer, PanelCard } from "../container";
|
|
9
|
+
import { FloatingNavbar, MapLoadingButton } from "../context-panel";
|
|
10
|
+
import { MapMarker } from "../helpers";
|
|
11
|
+
import { LocationPanel } from "../location-panel";
|
|
12
|
+
import { MapView } from "../map-view";
|
|
13
|
+
import styles from "./main.module.sass";
|
|
14
|
+
import { TileExtentLayer } from "./tile-extent";
|
|
15
|
+
import {
|
|
16
|
+
FeaturePanel,
|
|
17
|
+
FeatureSelectionHandler,
|
|
18
|
+
TileInfo,
|
|
19
|
+
} from "./vector-tile-features";
|
|
20
|
+
import { MapPosition } from "@macrostrat/mapbox-utils";
|
|
21
|
+
|
|
22
|
+
export enum MacrostratVectorTileset {
|
|
23
|
+
Carto = "carto",
|
|
24
|
+
CartoSlim = "carto-slim",
|
|
25
|
+
IGCPOrogens = "igcp-orogens",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export enum MacrostratRasterTileset {
|
|
29
|
+
Carto = "carto",
|
|
30
|
+
Emphasized = "emphasized",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const h = hyper.styled(styles);
|
|
34
|
+
|
|
35
|
+
export function DevMapPage({
|
|
36
|
+
title = "Map inspector",
|
|
37
|
+
headerElement = null,
|
|
38
|
+
transformRequest = null,
|
|
39
|
+
mapPosition = null,
|
|
40
|
+
mapboxToken = null,
|
|
41
|
+
overlayStyle = null,
|
|
42
|
+
children,
|
|
43
|
+
style,
|
|
44
|
+
focusedSource = null,
|
|
45
|
+
focusedSourceTitle = null,
|
|
46
|
+
projection = null,
|
|
47
|
+
}: {
|
|
48
|
+
headerElement?: React.ReactElement;
|
|
49
|
+
transformRequest?: mapboxgl.TransformRequestFunction;
|
|
50
|
+
title?: string;
|
|
51
|
+
style: mapboxgl.Style | string;
|
|
52
|
+
children?: React.ReactNode;
|
|
53
|
+
mapboxToken?: string;
|
|
54
|
+
overlayStyle?: mapboxgl.Style | string;
|
|
55
|
+
focusedSource?: string;
|
|
56
|
+
focusedSourceTitle?: string;
|
|
57
|
+
projection?: string;
|
|
58
|
+
mapPosition?: MapPosition;
|
|
59
|
+
}) {
|
|
60
|
+
/* We apply a custom style to the panel container when we are interacting
|
|
61
|
+
with the search bar, so that we can block map interactions until search
|
|
62
|
+
bar focus is lost.
|
|
63
|
+
We also apply a custom style when the infodrawer is open so we can hide
|
|
64
|
+
the search bar on mobile platforms
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
const dark = useDarkMode();
|
|
68
|
+
const isEnabled = dark?.isEnabled;
|
|
69
|
+
|
|
70
|
+
if (mapboxToken != null) {
|
|
71
|
+
mapboxgl.accessToken = mapboxToken;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
style ??= isEnabled
|
|
75
|
+
? "mapbox://styles/mapbox/dark-v10"
|
|
76
|
+
: "mapbox://styles/mapbox/light-v10";
|
|
77
|
+
|
|
78
|
+
const [isOpen, setOpen] = useState(false);
|
|
79
|
+
|
|
80
|
+
const [state, setState] = useStoredState("macrostrat:dev-map-page", {
|
|
81
|
+
showTileExtent: false,
|
|
82
|
+
xRay: false,
|
|
83
|
+
});
|
|
84
|
+
const { showTileExtent, xRay } = state;
|
|
85
|
+
|
|
86
|
+
const [actualStyle, setActualStyle] = useState(style);
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
buildInspectorStyle(style, overlayStyle, {
|
|
90
|
+
mapboxToken,
|
|
91
|
+
inDarkMode: isEnabled,
|
|
92
|
+
xRay,
|
|
93
|
+
}).then(setActualStyle);
|
|
94
|
+
}, [style, xRay, mapboxToken, isEnabled, overlayStyle]);
|
|
95
|
+
|
|
96
|
+
const [inspectPosition, setInspectPosition] =
|
|
97
|
+
useState<mapboxgl.LngLat | null>(null);
|
|
98
|
+
|
|
99
|
+
const [data, setData] = useState(null);
|
|
100
|
+
|
|
101
|
+
const onSelectPosition = useCallback((position: mapboxgl.LngLat) => {
|
|
102
|
+
setInspectPosition(position);
|
|
103
|
+
}, []);
|
|
104
|
+
|
|
105
|
+
let detailElement = null;
|
|
106
|
+
if (inspectPosition != null) {
|
|
107
|
+
detailElement = h(
|
|
108
|
+
LocationPanel,
|
|
109
|
+
{
|
|
110
|
+
onClose() {
|
|
111
|
+
setInspectPosition(null);
|
|
112
|
+
},
|
|
113
|
+
position: inspectPosition,
|
|
114
|
+
},
|
|
115
|
+
[
|
|
116
|
+
h(TileInfo, {
|
|
117
|
+
feature: data?.[0] ?? null,
|
|
118
|
+
showExtent: showTileExtent,
|
|
119
|
+
setShowExtent() {
|
|
120
|
+
setState({ ...state, showTileExtent: !showTileExtent });
|
|
121
|
+
},
|
|
122
|
+
}),
|
|
123
|
+
h(FeaturePanel, { features: data, focusedSource, focusedSourceTitle }),
|
|
124
|
+
]
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let tile = null;
|
|
129
|
+
if (showTileExtent && data?.[0] != null) {
|
|
130
|
+
let f = data[0];
|
|
131
|
+
tile = { x: f._x, y: f._y, z: f._z };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return h(
|
|
135
|
+
MapAreaContainer,
|
|
136
|
+
{
|
|
137
|
+
navbar: h(FloatingNavbar, [
|
|
138
|
+
headerElement ?? h("h2", title),
|
|
139
|
+
h(Spacer),
|
|
140
|
+
h(MapLoadingButton, {
|
|
141
|
+
active: isOpen,
|
|
142
|
+
onClick: () => setOpen(!isOpen),
|
|
143
|
+
}),
|
|
144
|
+
]),
|
|
145
|
+
contextPanel: h(PanelCard, [
|
|
146
|
+
h(Switch, {
|
|
147
|
+
checked: xRay,
|
|
148
|
+
label: "X-ray mode",
|
|
149
|
+
onChange() {
|
|
150
|
+
setState({ ...state, xRay: !xRay });
|
|
151
|
+
},
|
|
152
|
+
}),
|
|
153
|
+
children,
|
|
154
|
+
]),
|
|
155
|
+
detailPanel: detailElement,
|
|
156
|
+
contextPanelOpen: isOpen,
|
|
157
|
+
},
|
|
158
|
+
h(
|
|
159
|
+
MapView,
|
|
160
|
+
{
|
|
161
|
+
style: actualStyle,
|
|
162
|
+
transformRequest,
|
|
163
|
+
mapPosition,
|
|
164
|
+
projection: "globe",
|
|
165
|
+
},
|
|
166
|
+
[
|
|
167
|
+
h(FeatureSelectionHandler, {
|
|
168
|
+
selectedLocation: inspectPosition,
|
|
169
|
+
setFeatures: setData,
|
|
170
|
+
}),
|
|
171
|
+
h(MapMarker, {
|
|
172
|
+
position: inspectPosition,
|
|
173
|
+
setPosition: onSelectPosition,
|
|
174
|
+
}),
|
|
175
|
+
h(TileExtentLayer, { tile, color: isEnabled ? "white" : "black" }),
|
|
176
|
+
]
|
|
177
|
+
)
|
|
178
|
+
);
|
|
179
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
.feature-panel
|
|
2
|
+
position: relative
|
|
3
|
+
overflow-x: hidden
|
|
4
|
+
|
|
5
|
+
.key-value
|
|
6
|
+
display: inline-block
|
|
7
|
+
margin-right: 1em
|
|
8
|
+
.key
|
|
9
|
+
font-weight: bold
|
|
10
|
+
font-size: 0.9em
|
|
11
|
+
&:after
|
|
12
|
+
content: ': '
|
|
13
|
+
.value
|
|
14
|
+
font-size: 0.9em
|
|
15
|
+
|
|
16
|
+
.feature-properties
|
|
17
|
+
position: relative
|
|
18
|
+
&:before
|
|
19
|
+
content: "–"
|
|
20
|
+
position: absolute
|
|
21
|
+
top: 4px
|
|
22
|
+
left: 0
|
|
23
|
+
|
|
24
|
+
.feature-header h3
|
|
25
|
+
margin-bottom: 0
|
|
26
|
+
margin-top: 0.5em
|
|
27
|
+
|
|
28
|
+
.feature-group
|
|
29
|
+
border-bottom: 1px solid var(--panel-rule-inner)
|
|
30
|
+
padding: 0 1em
|
|
31
|
+
margin-left: -1em
|
|
32
|
+
margin-right: -1em
|
|
33
|
+
margin-bottom: 0.5em
|
|
34
|
+
&:last-child
|
|
35
|
+
border-bottom: none
|
|
36
|
+
|
|
37
|
+
.tile-info
|
|
38
|
+
display: flex
|
|
39
|
+
flex-direction: row
|
|
40
|
+
align-items: baseline
|
|
41
|
+
padding: 0 1em
|
|
42
|
+
h3
|
|
43
|
+
margin-right: 0.5em
|
|
44
|
+
|
|
45
|
+
.opacity-slider
|
|
46
|
+
margin: 0 1em 0.5em
|
|
47
|
+
:global
|
|
48
|
+
.bp4-slider-handle .bp4-slider-label
|
|
49
|
+
background-color: var(--secondary-color)
|
|
50
|
+
color: var(--text-color)
|
|
51
|
+
|
|
52
|
+
.unit-number
|
|
53
|
+
.unit
|
|
54
|
+
font-size: 0.9em
|
|
55
|
+
margin-left: 0.2em
|
|
56
|
+
font-weight: bold
|
|
57
|
+
|
|
58
|
+
.page
|
|
59
|
+
margin: 2em auto
|
|
60
|
+
max-width: 50em
|
|
61
|
+
|
|
62
|
+
.dev-index-page
|
|
63
|
+
overflow-y: scroll
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useMapConditionalStyle, useMapRef } from "@macrostrat/mapbox-react";
|
|
2
|
+
import { tileToGeoJSON } from "@mapbox/tilebelt";
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
|
|
5
|
+
type TileIndex = { x: number; y: number; z: number };
|
|
6
|
+
|
|
7
|
+
export function TileExtentLayer({
|
|
8
|
+
tile,
|
|
9
|
+
color = "red",
|
|
10
|
+
}: {
|
|
11
|
+
tile: TileIndex | null;
|
|
12
|
+
color?: string;
|
|
13
|
+
}) {
|
|
14
|
+
const styleCallback = useCallback(
|
|
15
|
+
(map, val: TileIndex) => {
|
|
16
|
+
const style = map.getStyle();
|
|
17
|
+
if (style.layers == null) return;
|
|
18
|
+
style.layers = style.layers.filter((l) => l.id != "tile-extent");
|
|
19
|
+
if (val == null) {
|
|
20
|
+
return map.setStyle(style);
|
|
21
|
+
}
|
|
22
|
+
const { x, y, z } = val;
|
|
23
|
+
const extent = tileToGeoJSON([x, y, z]);
|
|
24
|
+
const source = {
|
|
25
|
+
type: "geojson",
|
|
26
|
+
data: extent,
|
|
27
|
+
};
|
|
28
|
+
const layer = {
|
|
29
|
+
id: "tile-extent",
|
|
30
|
+
type: "line",
|
|
31
|
+
source: "tile-extent",
|
|
32
|
+
paint: {
|
|
33
|
+
"line-color": color,
|
|
34
|
+
"line-width": 2,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
style.sources["tile-extent"] = source;
|
|
38
|
+
style.layers.push(layer);
|
|
39
|
+
map.setStyle(style);
|
|
40
|
+
},
|
|
41
|
+
[color]
|
|
42
|
+
);
|
|
43
|
+
const map = useMapRef();
|
|
44
|
+
useMapConditionalStyle(map, tile, styleCallback);
|
|
45
|
+
return null;
|
|
46
|
+
}
|