Skip to content

Commit

Permalink
CragMap: Add photo locations
Browse files Browse the repository at this point in the history
  • Loading branch information
jvaclavik committed Dec 17, 2024
1 parent cc853c2 commit 336684a
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 2 deletions.
46 changes: 46 additions & 0 deletions src/components/FeaturePanel/Climbing/CameraMarker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import styled from '@emotion/styled';

type CameraTopDownMarkerProps = {
width?: number;
height?: number;
index?: number;
azimuth?: number; // Směr v úhlech, 0 = nahoru
onClick?: () => void;
};

const ARROW_SCALE = 2;

export const CameraMarker = ({
width,
height,
azimuth = 0,
index,
onClick,
}: CameraTopDownMarkerProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 55 55"
role="img"
height={height}
width={width}
>
<g transform="translate(11.5,11.5)" cursor="pointer" onClick={onClick}>
<g
transform={`rotate(${azimuth},16,16) translate(16,16) scale(${ARROW_SCALE}) translate(-16,-16)`}
>
<polygon points="16,2 10,14 22,14" fill="white" />
</g>

<path
fill="#000"
d="M26 7h-3.465l-1.704-2.555A1 1 0 0 0 20 4h-8a1 1 0 0 0-.831.445L9.464 7H6a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h20a3 3 0 0 0 3-3V10a3 3 0 0 0-3-3Z"
/>

<text fill="white" x={12} y={23} fontWeight="bold">
{index}
</text>
</g>
</svg>
);
126 changes: 124 additions & 2 deletions src/components/FeaturePanel/Climbing/CragMap.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import React, { useCallback, useState } from 'react';
import maplibregl, { GeoJSONSource } from 'maplibre-gl';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import maplibregl, { GeoJSONSource, LngLatLike, PointLike } from 'maplibre-gl';
import { outdoorStyle } from '../../Map/styles/outdoorStyle';
import { COMPASS_TOOLTIP } from '../../Map/useAddTopRightControls';
import styled from '@emotion/styled';
import { useFeatureContext } from '../../utils/FeatureContext';
import type { LayerSpecification } from '@maplibre/maplibre-gl-style-spec';
import { CircularProgress } from '@mui/material';
import { useClimbingContext } from './contexts/ClimbingContext';
import { addFilePrefix } from './utils/photo';
import metadata from 'next/dist/server/typescript/rules/metadata';
import { string } from 'prop-types';
import ReactDOMServer from 'react-dom/server';
import { AlphabeticalMarker } from '../../Directions/TextMarker';
import { getGlobalMap } from '../../../services/mapStorage';
import { CameraMarker } from './CameraMarker';
import Router from 'next/router';
import { getOsmappLink } from '../../../services/helpers';

const Map = styled.div<{ $isVisible: boolean }>`
visibility: ${({ $isVisible }) => ($isVisible ? 'visible' : 'hidden')};
Expand Down Expand Up @@ -75,11 +91,117 @@ export const routes: LayerSpecification[] = [
},
];

const useGetPhotoExifs = (photoPaths) => {
const [photoExifs, setPhotoExifs] = useState<
Record<string, Record<string, any>>
>({});
useEffect(() => {
async function fetchExifData(photos) {
const encodedTitles = photos.map((t) => addFilePrefix(t)).join('|');
const url = `https://commons.wikimedia.org/w/api.php?action=query&prop=imageinfo&iiprop=metadata&titles=${encodedTitles}&format=json&origin=*`;
const response = await fetch(url);
const data = await response.json();
return data.query.pages;
}

fetchExifData(photoPaths).then((pages) => {
const data = Object.values(pages).reduce<Record<string, any>>(
(acc, item: any) => {
const metadata = item?.imageinfo?.[0]?.metadata.reduce(
(acc2, { name, value }) => ({ ...acc2, [name]: value }),
{},
);

return {
...acc,
[item.title]: metadata,
};
},
{},
);
setPhotoExifs(data);
});
}, [photoPaths]);
return photoExifs;
};

function parseFractionOrNumber(input) {
if (input.includes('/')) {
const [numerator, denominator] = input.split('/');
return parseFloat(numerator) / parseFloat(denominator);
} else {
return parseFloat(input);
}
}

const usePhotoMarkers = (photoExifs, mapRef) => {
const { feature } = useFeatureContext();
const getMarker = (index: number, azimuth: number | null) => {
let svgElement;
const photoPath = Object.keys(photoExifs)[index];
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
svgElement = document.createElement('div');
svgElement.innerHTML = ReactDOMServer.renderToStaticMarkup(
<CameraMarker
width={30}
index={index}
azimuth={azimuth}
onClick={() => {
console.log('___TU');
Router.push(
`${getOsmappLink(feature)}/climbing/photo/${photoPath}${window.location.hash}`,
);
}}
/>,
);
} else svgElement = undefined;

return {
color: 'salmon',
element: svgElement,
offset: [0, -10] as PointLike,
};
};

const markerRef = useRef<maplibregl.Marker>();

useEffect(() => {
Object.keys(photoExifs).map((key, index) => {
const exifItems = photoExifs[key];

if (exifItems && exifItems.GPSLongitude && exifItems.GPSLatitude) {
const marker = getMarker(
index,
exifItems.GPSImgDirection
? parseFractionOrNumber(exifItems.GPSImgDirection)
: null,
);
markerRef.current = new maplibregl.Marker(marker)
.setLngLat([
exifItems.GPSLongitude,
exifItems.GPSLatitude,
] as LngLatLike)
.addTo(mapRef.current);
}
});

return () => {
Object.keys(photoExifs).map((key) => {
markerRef.current?.remove();
});
};
}, [mapRef, markerRef, photoExifs]);
};

const useInitMap = () => {
const containerRef = React.useRef(null);
const mapRef = React.useRef<maplibregl.Map>(null);
const [isMapLoaded, setIsMapLoaded] = useState(false);
const { feature } = useFeatureContext();
const { photoPaths } = useClimbingContext();

const photoExifs = useGetPhotoExifs(photoPaths);
usePhotoMarkers(photoExifs, mapRef);

const getClimbingSource = useCallback(
() => mapRef.current.getSource('climbing') as GeoJSONSource | undefined,
Expand Down
2 changes: 2 additions & 0 deletions src/components/FeaturePanel/Climbing/utils/photo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { naturalSort } from './array';
export const getWikimediaCommonsKey = (index: number) =>
`wikimedia_commons${index === 0 ? '' : `:${index + 1}`}`;

export const addFilePrefix = (name: string) => `File:${name}`;

export const removeFilePrefix = (name: string) => name?.replace(/^File:/, '');

export const isWikimediaCommons = (tag: string) =>
Expand Down

0 comments on commit 336684a

Please sign in to comment.