Skip to content

Commit 1aaddf5

Browse files
authored
Merge pull request #73 from UW-Macrostrat/heatmap
Heatmap update
2 parents 96a93f8 + 2d6c7b6 commit 1aaddf5

File tree

1 file changed

+102
-222
lines changed

1 file changed

+102
-222
lines changed

pages/heatmap/+Page.client.ts

Lines changed: 102 additions & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -3,259 +3,139 @@ import { useAPIResult } from "@macrostrat/ui-components";
33
import {
44
MapAreaContainer,
55
MapView,
6+
buildInspectorStyle
67
} from "@macrostrat/map-interface";
7-
import { mapboxAccessToken, matomoToken, matomoApiURL } from "@macrostrat-web/settings";
8+
import { mapboxAccessToken, matomoToken, matomoApiURL, tileserverDomain } from "@macrostrat-web/settings";
89
import { Footer } from "~/components/general";
910
import { Divider, Spinner, Tabs, Tab } from "@blueprintjs/core";
1011
import { useEffect, useState } from "react";
12+
import { mergeStyles } from "@macrostrat/mapbox-utils";
13+
import { useDarkMode } from "@macrostrat/ui-components";
1114

1215

1316
export function Page() {
14-
const [coords, setCoords] = useState<Array<{ latitude: number; longitude: number }> | null>(null);
15-
const today = getTodayCoords();
16-
17-
useEffect(() => {
18-
(async () => {
19-
const data = await getAllCoords();
20-
setCoords(data);
21-
})();
22-
}, []);
23-
24-
console.log("Coordinates:", coords);
25-
2617
return h('div.main', [
2718
h('div.heatmap-page', [
28-
h(PageHeader, { coords }),
29-
h(
30-
Tabs,
31-
{
32-
id: 'heatmap-tabs',
33-
},
34-
[
35-
h(Tab, { id: 'today', title: 'Today', panelClassName: 'today-tab-panel', panel: h(TodayMap, { today }) }),
36-
h(Tab, { id: 'all', title: 'All', panelClassName: 'all-tab-panel', panel: h(AllMap, { coords, today }) }),
37-
]
38-
)
19+
h(PageHeader),
20+
h(HeatMap)
3921
]),
4022
h(Footer)
4123
]);
4224
}
4325

44-
function PageHeader({ coords }) {
45-
const visitsToday = getVisitsToday();
46-
47-
const { visits, visitors } = visitsToday || {};
48-
49-
const Visit = !visitsToday ?
50-
h('p', 'Loading visits...') :
51-
h('div.visits-today', [
52-
h('h3', `${visits.toLocaleString()} visits today`),
53-
h.if(coords?.length)('h3', `${coords?.length?.toLocaleString()} visits this year`),
54-
])
55-
26+
function PageHeader() {
5627
return h('div.page-header', [
5728
h('h1', 'Heatmap'),
58-
Visit,
5929
h(Divider),
60-
h('p', 'This is a heatmap of all the locations where Macrostrat has been accessed.'),
30+
h('p', 'This is a heatmap of all the locations where Rockd has been accessed.'),
6131
h('p', 'The blue markers indicate today\'s accesses, while the grey markers indicate accesses from other days.'),
6232
]);
6333
}
6434

65-
function AllMap({coords, today}) {
66-
if (!coords || !today) {
67-
return h("div.map-area-container.loading", [
68-
h(Spinner, { size: 50 }),
69-
]);
70-
}
71-
72-
const handleMapLoaded = (map) => {
73-
map.on('load', () => {
74-
// Combine coords and today coords, marking today's points
75-
const allFeatures = coords.map((coord) => ({
76-
type: 'Feature',
77-
geometry: {
78-
type: 'Point',
79-
coordinates: [coord.longitude, coord.latitude],
80-
},
81-
properties: {
82-
isToday: false,
83-
},
84-
})).concat(
85-
today.map((coord) => ({
86-
type: 'Feature',
87-
geometry: {
88-
type: 'Point',
89-
coordinates: [coord.longitude, coord.latitude],
90-
},
91-
properties: {
92-
isToday: true,
93-
},
94-
}))
95-
);
96-
97-
map.addSource('markers', {
98-
type: 'geojson',
99-
data: {
100-
type: 'FeatureCollection',
101-
features: allFeatures,
102-
},
103-
});
104-
105-
// Individual points - others (grey)
106-
map.addLayer({
107-
id: 'markers-other',
108-
type: 'circle',
109-
source: 'markers',
110-
filter: ['all', ['!', ['has', 'point_count']], ['==', ['get', 'isToday'], false]],
111-
paint: {
112-
'circle-radius': 2,
113-
'circle-color': '#888',
114-
},
115-
});
116-
117-
// Individual points - today (blue)
118-
map.addLayer({
119-
id: 'markers-today',
120-
type: 'circle',
121-
source: 'markers',
122-
filter: ['all', ['!', ['has', 'point_count']], ['==', ['get', 'isToday'], true]],
123-
paint: {
124-
'circle-radius': 3,
125-
'circle-color': '#007cbf',
126-
},
127-
});
128-
});
129-
};
35+
function todayStyle() {
36+
return {
37+
sources: {
38+
today: {
39+
type: "vector",
40+
tiles: [ tileserverDomain + "/usage-stats/rockd/{z}/{x}/{y}?today=true" ],
41+
}
42+
},
43+
layers: [
44+
{
45+
id: 'today-points',
46+
type: 'circle',
47+
source: 'today',
48+
"source-layer": "default",
49+
paint: {
50+
'circle-color': "#373ec4",
51+
'circle-radius': 4,
52+
}
53+
},
54+
],
55+
};
56+
}
13057

131-
return h(MapInner, { handleMapLoaded });
58+
function allStyle() {
59+
return {
60+
sources: {
61+
all: {
62+
type: "vector",
63+
tiles: [ tileserverDomain + "/usage-stats/rockd/{z}/{x}/{y}"],
64+
}
65+
},
66+
layers: [
67+
{
68+
id: 'all-points',
69+
type: 'circle',
70+
source: 'all',
71+
"source-layer": "default",
72+
paint: {
73+
'circle-color': "#838383",
74+
'circle-radius': 4,
75+
}
76+
},
77+
],
78+
};
13279
}
13380

134-
function MapInner({handleMapLoaded}) {
135-
const style = 'mapbox://styles/mapbox/dark-v10';
13681

137-
return h(
138-
MapAreaContainer,
139-
{
140-
className: "map-area-container",
141-
},
82+
function HeatMap({
83+
mapboxToken,
84+
}: {
85+
headerElement?: React.ReactElement;
86+
title?: string;
87+
children?: React.ReactNode;
88+
mapboxToken?: string;
89+
}) {
90+
91+
const style = useMapStyle();
92+
if(style == null) return null;
93+
94+
const mapPosition = {
95+
camera: {
96+
lat: 39,
97+
lng: -98,
98+
altitude: 6000000,
99+
},
100+
};
101+
102+
return h(
103+
"div.map-container",
142104
[
143-
h(MapView, {
144-
style,
145-
mapboxToken: mapboxAccessToken,
146-
onMapLoaded: handleMapLoaded,
147-
mapPosition: {
148-
camera: {
149-
lat: 39,
150-
lng: -98,
151-
altitude: 6000000,
152-
},
153-
},
154-
}),
105+
// The Map Area Container
106+
h(
107+
MapAreaContainer,
108+
{
109+
className: 'map-area-container',
110+
},
111+
[
112+
h(MapView, { style, mapboxToken: mapboxAccessToken, mapPosition }),
113+
]
114+
),
155115
]
156-
);
157-
}
158-
159-
function TodayMap({today}) {
160-
if (!today) {
161-
return h("div.map-area-container.loading", [
162-
h(Spinner, { size: 50 }),
163-
]);
164-
}
165-
166-
const handleMapLoaded = (map) => {
167-
map.on('load', () => {
168-
// Combine coords and today coords, marking today's points
169-
const allFeatures = today.map((coord) => ({
170-
type: 'Feature',
171-
geometry: {
172-
type: 'Point',
173-
coordinates: [coord.longitude, coord.latitude],
174-
},
175-
}))
176-
177-
map.addSource('markers', {
178-
type: 'geojson',
179-
data: {
180-
type: 'FeatureCollection',
181-
features: allFeatures,
182-
},
183-
});
184-
185-
map.addLayer({
186-
id: 'markers-today',
187-
type: 'circle',
188-
source: 'markers',
189-
paint: {
190-
'circle-radius': 3,
191-
'circle-color': '#007cbf',
192-
},
193-
});
194-
});
195-
};
196-
197-
return h(MapInner, { handleMapLoaded });
116+
);
198117
}
199118

200-
async function getAllCoords(): Promise<Array<{ latitude: number, longitude: number }>> {
201-
const allCoords: Array<{ latitude: number, longitude: number }> = [];
202-
const pageSize = 10000;
203-
let offset = 0;
204-
let hasMore = true;
205-
206-
while (hasMore) {
207-
const result = await fetch(`${matomoApiURL}?${new URLSearchParams({
208-
date: '2025-07-01,today',
209-
period: 'range',
210-
filter_limit: pageSize.toString(),
211-
filter_offset: offset.toString(),
212-
showColumns: 'latitude,longitude',
213-
doNotFetchActions: 'true',
214-
module: 'API',
215-
idSite: '1',
216-
format: 'json',
217-
token_auth: matomoToken,
218-
method: 'Live.getLastVisitsDetails',
219-
})}`).then(res => res.json());
220-
221-
if (Array.isArray(result) && result.length > 0) {
222-
allCoords.push(...result);
223-
offset += pageSize;
224-
if (result.length < pageSize) {
225-
hasMore = false;
226-
}
227-
} else {
228-
hasMore = false;
229-
}
230-
}
119+
function useMapStyle() {
120+
const dark = useDarkMode();
121+
const isEnabled = dark?.isEnabled;
231122

232-
return allCoords;
233-
}
123+
const baseStyle = isEnabled
124+
? "mapbox://styles/mapbox/dark-v10"
125+
: "mapbox://styles/mapbox/light-v10";
234126

127+
const [actualStyle, setActualStyle] = useState(null);
128+
const overlayStyle = mergeStyles(allStyle(), todayStyle());
235129

236-
function getTodayCoords(): Array<{ latitude: number, longitude: number }> | undefined {
237-
return useAPIResult(matomoApiURL, {
238-
date: 'today',
239-
period: 'day',
240-
filter_limit: 10000,
241-
filter_offset: 0,
242-
module: 'API',
243-
format: 'json',
244-
showColumns: 'latitude,longitude',
245-
doNotFetchActions: true,
246-
idSite: '1',
247-
method: 'Live.getLastVisitsDetails',
248-
token_auth: matomoToken
249-
})
250-
}
130+
// Auto select sample type
131+
useEffect(() => {
132+
buildInspectorStyle(baseStyle, overlayStyle, {
133+
mapboxToken: mapboxAccessToken,
134+
inDarkMode: isEnabled,
135+
}).then((s) => {
136+
setActualStyle(s);
137+
});
138+
}, [isEnabled]);
251139

252-
function getVisitsToday(): { visits: number, visitors: number } | undefined {
253-
return useAPIResult(matomoApiURL, {
254-
method: "Live.getCounters",
255-
lastMinutes: 1440,
256-
module: 'API',
257-
format: 'json',
258-
idSite: '1',
259-
token_auth: matomoToken
260-
})?.[0]
140+
return actualStyle;
261141
}

0 commit comments

Comments
 (0)