diff --git a/src/components/FeaturePanel/Climbing/CameraMarker.tsx b/src/components/FeaturePanel/Climbing/CameraMarker.tsx new file mode 100644 index 00000000..e2e11ed6 --- /dev/null +++ b/src/components/FeaturePanel/Climbing/CameraMarker.tsx @@ -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) => ( + + + + + + + + + + {index} + + + +); diff --git a/src/components/FeaturePanel/Climbing/CragMap.tsx b/src/components/FeaturePanel/Climbing/CragMap.tsx index 82f456e0..0725e261 100644 --- a/src/components/FeaturePanel/Climbing/CragMap.tsx +++ b/src/components/FeaturePanel/Climbing/CragMap.tsx @@ -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')}; @@ -75,11 +91,117 @@ export const routes: LayerSpecification[] = [ }, ]; +const useGetPhotoExifs = (photoPaths) => { + const [photoExifs, setPhotoExifs] = useState< + Record> + >({}); + 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>( + (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( + { + 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(); + + 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(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, diff --git a/src/components/FeaturePanel/Climbing/utils/photo.ts b/src/components/FeaturePanel/Climbing/utils/photo.ts index 85f392cd..470d6aef 100644 --- a/src/components/FeaturePanel/Climbing/utils/photo.ts +++ b/src/components/FeaturePanel/Climbing/utils/photo.ts @@ -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) =>