feat: added google heatmap

Signed-off-by: Varun Raj <varun@skcript.com>
This commit is contained in:
Varun Raj
2024-12-30 22:36:48 +05:30
parent 31e58793be
commit 842e323042
11 changed files with 145 additions and 2 deletions
BIN
View File
Binary file not shown.
+1
View File
@@ -35,6 +35,7 @@
"@types/cookie": "^0.6.0",
"@types/qs": "^6.9.15",
"@types/react-calendar-heatmap": "^1.6.7",
"@vis.gl/react-google-maps": "^1.4.2",
"axios": "^1.7.4",
"chrono-node": "^1.4.9",
"class-variance-authority": "^0.7.0",
-1
View File
@@ -5,7 +5,6 @@ import { cn } from "@/lib/utils";
import { useRouter } from "next/router";
import dynamic from "next/dynamic";
import Image from "next/image";
import ProfileInfo from "./ProfileInfo";
const ThemeSwitcher = dynamic(() => import("@/components/shared/ThemeSwitcher"), {
+6 -1
View File
@@ -1,4 +1,4 @@
import { Album, GalleryHorizontal, GalleryVerticalEnd, Home, Image, LocateOff, MapPinX, Search, User } from "lucide-react";
import { Album, GalleryHorizontal, GalleryVerticalEnd, Home, Image, LocateOff, MapPin, MapPinX, Search, User } from "lucide-react";
export const sidebarNavs = [
{
@@ -31,5 +31,10 @@ export const sidebarNavs = [
link: "/albums",
icon: <GalleryHorizontal className="h-4 w-4" />,
},
{
title: "Geo Heatmap",
link: "/assets/geo-heatmap",
icon: <MapPin className="h-4 w-4" />,
}
];
+1
View File
@@ -7,6 +7,7 @@ export const ENV = {
SECURE_COOKIE: process.env.SECURE_COOKIE === 'true',
VERSION: process.env.VERSION,
GEMINI_API_KEY: process.env.GEMINI_API_KEY as string,
GOOGLE_MAPS_API_KEY: process.env.GOOGLE_MAPS_API_KEY as string,
};
+1
View File
@@ -34,6 +34,7 @@ export const UPDATE_ASSETS_PATH = BASE_PROXY_ENDPOINT + "/assets";
export const ASSET_THUMBNAIL_PATH = (id: string) => BASE_PROXY_ENDPOINT + "/asset/thumbnail/" + id;
export const ASSET_PREVIEW_PATH = (id: string) => BASE_PROXY_ENDPOINT + "/asset/thumbnail/" + id + "?size=preview";
export const ASSET_VIDEO_PATH = (id: string) => BASE_PROXY_ENDPOINT + "/asset/video/" + id;
export const ASSET_GEO_HEATMAP_PATH = BASE_API_ENDPOINT + "/assets/geo-heatmap";
// Location
+1
View File
@@ -5,6 +5,7 @@ export interface ConfigContextType {
exImmichUrl: string;
version?: string;
geminiEnabled: boolean;
googleMapsApiKey: string;
}
const ConfigContext = createContext<ConfigContextType>({
+5
View File
@@ -1,5 +1,6 @@
import {
ADD_ASSETS_ALBUMS_PATH,
ASSET_GEO_HEATMAP_PATH,
FIND_ASSETS,
LIST_ALBUMS_PATH,
LIST_MISSING_LOCATION_ASSETS_PATH,
@@ -50,4 +51,8 @@ export const updateAssets = async (params: IUpdateAssetsParams) => {
export const findAssets = async (query: string) => {
return API.post(FIND_ASSETS, { query });
}
export const getAssetGeoHeatmap = async () => {
return API.get(ASSET_GEO_HEATMAP_PATH);
}
+1
View File
@@ -30,6 +30,7 @@ App.getInitialProps = async () => {
immichURL: ENV.IMMICH_URL,
version: ENV.VERSION,
geminiEnabled: !!ENV.GEMINI_API_KEY?.length,
googleMapsApiKey: ENV.GOOGLE_MAPS_API_KEY,
},
};
};
+33
View File
@@ -0,0 +1,33 @@
import { NextApiResponse } from "next";
import { db } from "@/config/db";
import { getCurrentUser } from "@/handlers/serverUtils/user.utils";
import { NextApiRequest } from "next";
import { assets, exif } from "@/schema";
import { and, eq, isNotNull } from "drizzle-orm";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const currentUser = await getCurrentUser(req);
if (!currentUser) {
return res.status(401).json({ message: 'Unauthorized' });
}
const dbAssets = await db.select({
assetId: assets.id,
latitude: exif.latitude,
longitude: exif.longitude,
}).from(assets)
.innerJoin(exif, eq(assets.id, exif.assetId))
.where(
and(
eq(assets.ownerId, currentUser.id),
isNotNull(exif.latitude),
isNotNull(exif.longitude)
)
);
const heatmapData = dbAssets.map((asset) => [
asset.longitude,
asset.latitude,
]);
res.status(200).json(heatmapData);
}
+96
View File
@@ -0,0 +1,96 @@
import Header from '@/components/shared/Header'
import { useConfig } from '@/contexts/ConfigContext';
import React, { useEffect, useMemo, useState } from 'react'
import { useMap, useMapsLibrary } from '@vis.gl/react-google-maps';
import { APIProvider, Map } from '@vis.gl/react-google-maps';
import PageLayout from '@/components/layouts/PageLayout';
import { getAssetGeoHeatmap } from '@/handlers/api/asset.handler';
import { useTheme } from 'next-themes';
import { MapPinX } from 'lucide-react';
const HeatMap = () => {
const map = useMap();
const visualization = useMapsLibrary('visualization');
const heatmap = useMemo(() => {
if (!visualization) return null;
return new google.maps.visualization.HeatmapLayer({
radius: 20,
opacity: 0.5
});
}, [visualization]);
useEffect(() => {
if (!heatmap) return;
getAssetGeoHeatmap().then((data) => {
console.log(data);
heatmap.setData(data.map(([lng, lat]: [number, number]) => ({
location: new google.maps.LatLng(lat, lng),
weight: 10
})));
});
}, [heatmap]);
useEffect(() => {
if (!heatmap) return;
heatmap.setMap(map);
return () => {
heatmap.setMap(null);
};
}, [heatmap, map]);
return null;
}
export default function GeoHeatmap() {
const { googleMapsApiKey } = useConfig();
const { theme } = useTheme();
const [mapId, setMapId] = useState('7a9e2ebecd32a903');
useEffect(() => {
if (theme === 'light') {
setMapId("");
} else {
setMapId('7a9e2ebecd32a903');
}
}, [theme]);
return (
<PageLayout className="!p-0 !mb-0">
<Header leftComponent="Geo Heatmap" />
<div className='h-full w-full'>
{googleMapsApiKey ? (
<APIProvider apiKey={googleMapsApiKey}>
<Map
defaultCenter={{ lat: 0, lng: 0 }}
mapId={mapId}
defaultZoom={2}
gestureHandling={'greedy'}
disableDefaultUI={true}
className='h-full w-full'
/>
<HeatMap />
</APIProvider>
) : (
<div className='h-full w-full flex items-center justify-center flex-col gap-2'>
<MapPinX className='w-10 h-10 text-muted-foreground' />
<p className='text-muted-foreground'>No Google Maps API key found</p>
<p className='text-muted-foreground'>
Please create and add your Google Maps API key in the env file under <kbd className='bg-zinc-200 text-black dark:text-white px-1 py-0.5 rounded-md dark:bg-zinc-500'>GOOGLE_MAPS_API_KEY</kbd>.
</p>
</div>
)}
</div>
</PageLayout>
)
}