diff --git a/pages/api/og-image.tsx b/pages/api/og-image.tsx
index b51d16d99..41b0e4dd6 100644
--- a/pages/api/og-image.tsx
+++ b/pages/api/og-image.tsx
@@ -76,8 +76,8 @@ const renderSvg = async (
export default async (req: NextApiRequest, res: NextApiResponse) => {
const t1 = Date.now();
try {
- const shortId = getApiId(req.query.id);
- const feature = await fetchWithMemberFeatures(shortId);
+ const osmId = getApiId(req.query.id);
+ const feature = await fetchWithMemberFeatures(osmId);
const def = feature.imageDefs?.[0]; // TODO iterate when first not found
if (!def) {
throw new Error('No image definition found');
diff --git a/src/components/FeaturePanel/CragsInArea.tsx b/src/components/FeaturePanel/CragsInArea.tsx
index d6bbbdc36..4553bf65d 100644
--- a/src/components/FeaturePanel/CragsInArea.tsx
+++ b/src/components/FeaturePanel/CragsInArea.tsx
@@ -1,17 +1,17 @@
import styled from '@emotion/styled';
-import { Box, useTheme } from '@mui/material';
+import { Box } from '@mui/material';
import React from 'react';
import Router from 'next/router';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import { useFeatureContext } from '../utils/FeatureContext';
import { getOsmappLink, getUrlOsmId } from '../../services/helpers';
-import { Feature } from '../../services/types';
+import { Feature, isInstant, OsmId } from '../../services/types';
import { useMobileMode } from '../helpers';
-import { getWikimediaCommonsKeys } from './Climbing/utils/photo';
-import { useScrollShadow } from './Climbing/utils/useScrollShadow';
import { getLabel } from '../../helpers/featureLabel';
-import { getCommonsImageUrl } from '../../services/images/getCommonsImageUrl';
+import { Slider, Wrapper } from './ImagePane/FeatureImages';
+import { Image } from './ImagePane/Image/Image';
+import { getInstantImage } from '../../services/images/getImageDefs';
const ArrowIcon = styled(ArrowForwardIosIcon)`
opacity: 0.2;
@@ -21,6 +21,7 @@ const HeadingRow = styled.div`
display: flex;
flex-direction: row;
align-items: center;
+ padding: 0 12px;
`;
const Container = styled.div`
overflow: auto;
@@ -30,7 +31,7 @@ const Container = styled.div`
justify-content: space-between;
cursor: pointer;
border-radius: 8px;
- padding: 12px;
+ padding: 12px 0;
background-color: ${({ theme }) => theme.palette.background.elevation};
&:hover {
${ArrowIcon} {
@@ -44,12 +45,13 @@ const CragList = styled.div`
flex-direction: column;
gap: 12px;
`;
-const Anchor = styled.a`
+const Link = styled.a`
text-decoration: none !important;
`;
const Content = styled.div`
flex: 1;
`;
+
const CragName = styled.div`
padding: 0;
font-weight: 900;
@@ -64,108 +66,92 @@ const NumberOfRoutes = styled.div`
font-size: 13px;
color: ${({ theme }) => theme.palette.secondary.main};
`;
-const Gallery = styled.div`
- display: flex;
- gap: 8px;
- border-radius: 8px;
- overflow: auto;
- margin-top: 12px;
-`;
-const Image = styled.img`
- border-radius: 8px;
- height: 200px;
- flex: 1;
- object-fit: cover;
-`;
-const CragItem = ({ feature }: { feature: Feature }) => {
- const theme: any = useTheme();
+const Header = ({
+ imagesCount,
+ label,
+ routesCount,
+}: {
+ label: string;
+ routesCount: number;
+ imagesCount: number;
+}) => (
+
+
+ {label}{' '}
+
+ {routesCount > 0 && (
+ {routesCount} routes
+ )}
+ {imagesCount > 0 && (
+ {imagesCount} photos
+ )}
+
+
+
+
+);
+const Gallery = ({ images }) => {
+ return (
+
+
+ {images.map((item) => (
+
+ ))}
+
+
+ );
+};
+
+const getOnClickWithHash = (apiId: OsmId) => (e) => {
+ e.preventDefault();
+ Router.push(`/${getUrlOsmId(apiId)}${window.location.hash}`);
+};
+
+const CragItem = ({ feature }: { feature: Feature }) => {
const mobileMode = useMobileMode();
const { setPreview } = useFeatureContext();
- const { osmMeta } = feature;
- const handleClick = (e) => {
- e.preventDefault();
- setPreview(null);
- Router.push(`/${getUrlOsmId(osmMeta)}${window.location.hash}`);
- };
const handleHover = () => feature.center && setPreview(feature);
- const cragPhotoKeys = getWikimediaCommonsKeys(feature.tags);
+ const images =
+ feature?.imageDefs?.filter(isInstant)?.map((def) => ({
+ def,
+ image: getInstantImage(def),
+ })) ?? [];
- const {
- scrollElementRef,
- onScroll,
- ShadowContainer,
- ShadowLeft,
- ShadowRight,
- } = useScrollShadow();
return (
- setPreview(null)}
>
-
-
- {getLabel(feature)}{' '}
-
- {feature.members?.length > 0 && (
-
- {feature.members.length} routes{' '}
-
- )}
- {cragPhotoKeys.length > 0 && (
- {cragPhotoKeys.length} photos
- )}
-
-
-
-
- {cragPhotoKeys.length > 0 && (
-
-
-
- {cragPhotoKeys.map((cragPhotoTag) => {
- const photoPath = feature.tags[cragPhotoTag];
- const url = getCommonsImageUrl(photoPath, 410);
- return ;
- })}
-
-
-
- )}
+
+ {images.length ? : null}
-
+
);
};
+
export const CragsInArea = () => {
- const {
- feature: { memberFeatures, tags },
- } = useFeatureContext();
+ const { feature } = useFeatureContext();
- if (!memberFeatures?.length) {
+ if (!feature.memberFeatures?.length) {
return null;
}
-
- const isClimbingArea = tags.climbing === 'area';
- if (!isClimbingArea) {
+ if (feature.tags.climbing !== 'area') {
return null;
}
return (
- {memberFeatures.map((item) => (
+ {feature.memberFeatures.map((item) => (
))}
diff --git a/src/components/FeaturePanel/FeaturePanel.tsx b/src/components/FeaturePanel/FeaturePanel.tsx
index ad306ec1c..677a0806d 100644
--- a/src/components/FeaturePanel/FeaturePanel.tsx
+++ b/src/components/FeaturePanel/FeaturePanel.tsx
@@ -22,6 +22,7 @@ import { RouteDistributionInPanel } from './Climbing/RouteDistribution';
import { RouteListInPanel } from './Climbing/RouteList/RouteList';
import { FeaturePanelFooter } from './FeaturePanelFooter';
import { ClimbingRouteGrade } from './ClimbingRouteGrade';
+import { Box } from '@mui/material';
const Flex = styled.div`
flex: 1;
@@ -61,7 +62,9 @@ export const FeaturePanel = () => {
-
+
+
+
diff --git a/src/components/FeaturePanel/ImagePane/FeatureImages.tsx b/src/components/FeaturePanel/ImagePane/FeatureImages.tsx
index b77c440df..0fb8cddc2 100644
--- a/src/components/FeaturePanel/ImagePane/FeatureImages.tsx
+++ b/src/components/FeaturePanel/ImagePane/FeatureImages.tsx
@@ -6,12 +6,10 @@ import { useLoadImages } from './useLoadImages';
import { NoImage } from './NoImage';
import { HEIGHT, ImageSkeleton } from './helpers';
-const Wrapper = styled.div`
+export const Wrapper = styled.div`
width: 100%;
height: calc(${HEIGHT}px + 10px); // 10px for scrollbar
min-height: calc(${HEIGHT}px + 10px); // otherwise it shrinks b/c of flex
-
- margin-bottom: 16px;
`;
const StyledScrollbars = styled(Scrollbars)`
@@ -25,7 +23,7 @@ const StyledScrollbars = styled(Scrollbars)`
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
`;
-const Slider = ({ children }) => (
+export const Slider = ({ children }) => (
{children}
diff --git a/src/components/Map/behaviour/useInitMap.tsx b/src/components/Map/behaviour/useInitMap.tsx
index 6b1b8cbeb..a03b53a62 100644
--- a/src/components/Map/behaviour/useInitMap.tsx
+++ b/src/components/Map/behaviour/useInitMap.tsx
@@ -14,6 +14,7 @@ const filterConsoleLog = () => {
// eslint-disable-next-line no-console
console.warn = (message, ...optionalParams) => {
if (
+ typeof message === 'string' &&
!message.includes(
'Please make sure you have added the image with map.addImage',
)
diff --git a/src/components/Map/behaviour/useUpdateStyle.tsx b/src/components/Map/behaviour/useUpdateStyle.tsx
index 1efbad5a9..f2e742c39 100644
--- a/src/components/Map/behaviour/useUpdateStyle.tsx
+++ b/src/components/Map/behaviour/useUpdateStyle.tsx
@@ -81,9 +81,7 @@ export const useUpdateStyle = createMapEffectHook(
const style = cloneDeep(getBaseStyle(key));
addOverlaysToStyle(map, style, overlays);
- console.log('style', map.loaded(), map.getStyle()); // eslint-disable-line no-console
map.setStyle(style, { diff: map.loaded() });
- console.log('style2', map.loaded(), map.getStyle()); // eslint-disable-line no-console
setUpHover(map, layersWithOsmId(style));
},
diff --git a/src/services/osmApi.ts b/src/services/osmApi.ts
index ab1065de2..b706cfcb6 100644
--- a/src/services/osmApi.ts
+++ b/src/services/osmApi.ts
@@ -1,5 +1,5 @@
import { resolveCountryCode } from 'next-codegrid';
-import { FetchError, getApiId, getShortId, getUrlOsmId, prod } from './helpers';
+import { FetchError, getShortId, getUrlOsmId, prod } from './helpers';
import { fetchJson } from './fetch';
import { Feature, LonLat, OsmId, Position, SuccessInfo } from './types';
import { removeFetchCache } from './fetchCache';
@@ -12,6 +12,7 @@ import { getImageDefs, mergeMemberImageDefs } from './images/getImageDefs';
import * as Sentry from '@sentry/nextjs';
import { fetchOverpassCenter } from './overpass/fetchOverpassCenter';
import { isClimbingRelation, isClimbingRoute } from '../utils';
+import { getOverpassUrl } from './overpassSearch';
const getOsmUrl = ({ type, id }) =>
`https://api.openstreetmap.org/api/0.6/${type}/${id}.json`;
@@ -114,37 +115,82 @@ const getItemsMap = (elements) => {
return map;
};
+const getMemberFeatures = (members: Feature['members'], map) => {
+ return (
+ members
+ ?.map(({ type, ref, role }) => {
+ const element = map[type][ref];
+ if (!element) {
+ return null;
+ }
+
+ const feature = addSchemaToFeature(osmToFeature(element));
+ feature.osmMeta.role = role;
+ feature.center = element.center
+ ? [element.center.lon, element.center.lat] // from overpass "out center"
+ : undefined;
+ return feature;
+ })
+ .filter(Boolean) ?? []
+ );
+};
+
export const fetchWithMemberFeatures = async (apiId: OsmId) => {
- // TODO we can compute geometry using cragsToGeojson() and display it in the map
- const url =
- apiId.type === 'relation' ? getOsmFullUrl(apiId) : getOsmUrl(apiId);
- const full = await fetchJson(url);
+ if (apiId.type !== 'relation') {
+ const wayOrNode = await fetchJson(getOsmUrl(apiId));
+ return addSchemaToFeature(osmToFeature(wayOrNode));
+ }
+
+ const full = await fetchJson(getOsmFullUrl(apiId));
const map = getItemsMap(full.elements);
- const mainFeature = map[apiId.type][apiId.id];
+ const relation = map.relation[apiId.id];
+
+ const out: Feature = {
+ ...addSchemaToFeature(osmToFeature(relation)),
+ memberFeatures: getMemberFeatures(relation.members, map),
+ };
+ mergeMemberImageDefs(out);
+ return out;
+};
+const addMemberFeaturesToArea = async (relation: Feature) => {
+ const { tags, osmMeta } = relation;
+ const url = getOverpassUrl(`[out:json];rel(${osmMeta.id});>>;out center qt;`);
+ const overpass = await fetchJson(url);
+ const itemsMap = getItemsMap(overpass.elements);
+ const memberFeatures = getMemberFeatures(relation.members, itemsMap).map(
+ (memberFeature) => {
+ const crag: Feature = {
+ ...memberFeature,
+ memberFeatures: getMemberFeatures(memberFeature.members, itemsMap),
+ };
+ mergeMemberImageDefs(crag);
+ return crag;
+ },
+ );
+
+ return { ...relation, memberFeatures };
+};
+
+const addMemberFeaturesToRelation = async (relation: Feature) => {
+ const { tags, osmMeta: apiId } = relation;
if (apiId.type !== 'relation') {
- return addSchemaToFeature(osmToFeature(mainFeature));
+ throw new Error('addMemberFeaturesToRelation() called with non-relation');
}
- const memberFeatures =
- mainFeature.members.map(({ type, ref, role }) => {
- const element = map[type][ref];
- if (!element) {
- return null;
- }
+ if (tags.climbing === 'area') {
+ return await addMemberFeaturesToArea(relation);
+ }
- const feature = addSchemaToFeature(osmToFeature(element));
- feature.osmMeta.role = role;
- return feature;
- }) ?? [];
+ const full = await fetchJson(getOsmFullUrl(apiId));
+ const map = getItemsMap(full.elements);
- const featureWithMemberFeatures = {
- ...addSchemaToFeature(osmToFeature(mainFeature)),
- memberFeatures: memberFeatures.filter(Boolean),
+ const out: Feature = {
+ ...relation,
+ memberFeatures: getMemberFeatures(relation.members, map),
};
- mergeMemberImageDefs(featureWithMemberFeatures); // TODO test + only for crag
-
- return featureWithMemberFeatures;
+ mergeMemberImageDefs(out);
+ return out;
};
// TODO parent should be probably fetched for every feaure in fetchFeatureWithCenter()
@@ -162,8 +208,9 @@ export const addMembersAndParents = async (
if (isClimbingRelation(feature)) {
const [parentFeatures, featureWithMemberFeatures] = await Promise.all([
fetchParentFeatures(feature.osmMeta),
- fetchWithMemberFeatures(feature.osmMeta),
+ addMemberFeaturesToRelation(feature),
]);
+
return {
...featureWithMemberFeatures,
center: feature.center, // feature contains correct center from centerCache or overpass