Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map: Predefined views for landmarks #696

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions pages/camera-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { GetServerSideProps } from 'next';
import { views } from '../src/services/landmarks/views';

export const Config = () => null;

export const getServerSideProps: GetServerSideProps = async ({
res,
query,
}) => {
res.setHeader('Content-Type', 'application/json');

const responseBody = views[`${query.shortId}`] ?? null;
res.write(JSON.stringify(responseBody));
res.end();

return { props: {} };
};

export default Config;
23 changes: 16 additions & 7 deletions src/components/App/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Ref, useEffect, useRef } from 'react';
import React, { useEffect, useRef } from 'react';
import Cookies from 'js-cookie';

import nextCookies from 'next-cookies';
Expand Down Expand Up @@ -59,16 +59,25 @@ export const getMapViewFromHash = (): View | undefined => {
};

const useUpdateViewFromFeature = () => {
const { feature } = useFeatureContext();
const { setView } = useMapStateContext();
const { feature, accessMethod } = useFeatureContext();
const { setView, setLandmarkPitch, setLandmarkBearing } =
useMapStateContext();

React.useEffect(() => {
if (!feature?.center) return;
if (!feature) return;
if (!feature.center) return;
if (getMapViewFromHash()) return;
if (accessMethod === 'click') return;

const { landmarkView, center } = feature;

const [lon, lat] = center.map((deg) => deg.toFixed(4));
const zoom = landmarkView?.zoom ?? 17;
setView([`${zoom}`, lat, lon]);

const [lon, lat] = feature.center.map((deg) => deg.toFixed(4));
setView(['17.00', lat, lon]);
}, [feature, setView]);
setLandmarkBearing(landmarkView?.bearing);
setLandmarkPitch(landmarkView?.pitch);
}, [feature, setView, setLandmarkBearing, setLandmarkPitch, accessMethod]);
};

const useUpdateViewFromHash = () => {
Expand Down
7 changes: 7 additions & 0 deletions src/components/FeaturePanel/FeaturePanelFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Coordinates from './Coordinates';
import { t } from '../../services/intl';
import { ObjectsAround } from './ObjectsAround';
import React from 'react';
import { RecommendedView } from './RecommendedView';

type Props = {
advanced: boolean;
Expand All @@ -31,6 +32,12 @@ export const FeaturePanelFooter = ({
<PanelSidePadding>
<FeatureDescription advanced={advanced} setAdvanced={setAdvanced} />
<Coordinates />
{feature.landmarkView && (
<>
<br />
<RecommendedView />
</>
)}
<br />
<a href={osmappLink}>{osmappLink}</a>
<br />
Expand Down
42 changes: 42 additions & 0 deletions src/components/FeaturePanel/RecommendedView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import styled from '@emotion/styled';
import { Button } from '@mui/material';
import { useFeatureContext } from '../utils/FeatureContext';
import { getGlobalMap } from '../../services/mapStorage';
import isNumber from 'lodash/isNumber';
import { t } from '../../services/intl';

const SubtleButton = styled(Button)(({ theme }) => ({
textTransform: 'none',
backgroundColor: 'transparent',
color: theme.palette.text.primary,
border: `1px solid ${theme.palette.divider}`,
padding: '4px 8px',
margin: '4px 0',
fontSize: '0.75rem',
'&:hover': {
backgroundColor: theme.palette.action.hover,
borderColor: theme.palette.text.primary,
},
}));

export const RecommendedView = () => {
const { feature } = useFeatureContext();
return (
<SubtleButton
onClick={() => {
const { landmarkView } = feature;
const { zoom, pitch, bearing } = landmarkView;
const [lng, lat] = feature.center;
// flyTo breaks the map
getGlobalMap()?.jumpTo({
center: { lng, lat },
...(isNumber(zoom) ? { zoom } : {}),
...(isNumber(pitch) ? { pitch } : {}),
...(isNumber(bearing) ? { bearing } : {}),
});
}}
>
{t('featurepanel.recommended_view')}
</SubtleButton>
);
};
42 changes: 29 additions & 13 deletions src/components/Map/BrowserMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import { useUpdateStyle } from './behaviour/useUpdateStyle';
import { useInitMap } from './behaviour/useInitMap';
import { Translation } from '../../services/intl';
import { useToggleTerrainControl } from './behaviour/useToggleTerrainControl';
import { webglSupported } from './helpers';
import { supports3d, webglSupported } from './helpers';
import { useOnMapLongPressed } from './behaviour/useOnMapLongPressed';
import { useAddTopRightControls } from './useAddTopRightControls';
import isNumber from 'lodash/isNumber';

const useOnMapLoaded = createMapEventHook<'load', [MapEventHandler<'load'>]>(
(_, onMapLoaded) => ({
Expand All @@ -26,13 +27,22 @@ const useOnMapLoaded = createMapEventHook<'load', [MapEventHandler<'load'>]>(
}),
);

const useUpdateMap = createMapEffectHook<[View]>((map, viewForMap) => {
const center: [number, number] = [
parseFloat(viewForMap[2]),
parseFloat(viewForMap[1]),
];
map.jumpTo({ center, zoom: parseFloat(viewForMap[0]) });
});
const useUpdateMap = createMapEffectHook<[View, number, number, string[]]>(
(map, viewForMap, pitch, bearing, activeLayers) => {
const center: [number, number] = [
parseFloat(viewForMap[2]),
parseFloat(viewForMap[1]),
];

// flyTo makes the map unusable
map.jumpTo({
center,
zoom: parseFloat(viewForMap[0]),
...(supports3d(activeLayers) && isNumber(pitch) ? { pitch } : {}),
...(supports3d(activeLayers) && isNumber(bearing) ? { bearing } : {}),
});
},
);

const NotSupportedMessage = () => (
<span
Expand All @@ -47,21 +57,27 @@ const NotSupportedMessage = () => (
const BrowserMap = () => {
const { userLayers } = useMapStateContext();
const mobileMode = useMobileMode();
const { setFeature } = useFeatureContext();
const { setFeature, setAccessMethod } = useFeatureContext();
const { mapLoaded, setMapLoaded } = useMapStateContext();

const [map, mapRef] = useInitMap();
useAddTopRightControls(map, mobileMode);
useOnMapClicked(map, setFeature);
useOnMapClicked(map, setFeature, setAccessMethod);
useOnMapLongPressed(map, setFeature);
useOnMapLoaded(map, setMapLoaded);
useFeatureMarker(map);

const { viewForMap, setViewFromMap, setBbox, activeLayers } =
useMapStateContext();
const {
viewForMap,
setViewFromMap,
setBbox,
activeLayers,
landmarkPitch,
landmarkBearing,
} = useMapStateContext();
useUpdateViewOnMove(map, setViewFromMap, setBbox);
useToggleTerrainControl(map);
useUpdateMap(map, viewForMap);
useUpdateMap(map, viewForMap, landmarkPitch, landmarkBearing, activeLayers);
useUpdateStyle(map, activeLayers, userLayers, mapLoaded);

return <div ref={mapRef} style={{ height: '100%', width: '100%' }} />;
Expand Down
66 changes: 37 additions & 29 deletions src/components/Map/behaviour/useOnMapClicked.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { convertMapIdToOsmId, getIsOsmObject } from '../helpers';
import { maptilerFix } from './maptilerFix';
import { Feature, LonLat } from '../../../services/types';
import { createCoordsFeature, pushFeatureToRouter } from './utils';
import { SetFeature } from '../../utils/FeatureContext';
import { AccessMethod, SetFeature } from '../../utils/FeatureContext';
import { Map } from 'maplibre-gl';

const isSameOsmId = (feature: Feature, skeleton: Feature) =>
Expand Down Expand Up @@ -52,35 +52,43 @@ const coordsClicked = (map: Map, coords: LonLat, setFeature: SetFeature) => {
});
};

export const useOnMapClicked = createMapEventHook<'click', [SetFeature]>(
(map, setFeature) => ({
eventType: 'click',
eventHandler: async ({ point }) => {
const coords = map.unproject(point).toArray();
const features = map.queryRenderedFeatures(point);
if (!features.length) {
coordsClicked(map, coords, setFeature);
return;
}
export const useOnMapClicked = createMapEventHook<
'click',
[SetFeature, (accessMethod: AccessMethod) => void]
>((map, setFeature, setAccessMethod) => ({
eventType: 'click',
eventHandler: async ({ point }) => {
const coords = map.unproject(point).toArray();
const features = map.queryRenderedFeatures(point);
if (!features.length) {
coordsClicked(map, coords, setFeature);
return;
}

const skeleton = getSkeleton(features[0], coords);
console.log(`clicked map feature (id=${features[0].id}): `, features[0]); // eslint-disable-line no-console
publishDbgObject('last skeleton', skeleton);
const skeleton = getSkeleton(features[0], coords);
// eslint-disable-next-line no-console
console.log(
`clicked map feature (id=${features[0].id}): `,
features[0],
'shortId:',
getShortId(skeleton.osmMeta),
); // eslint-disable-line no-console
publishDbgObject('last skeleton', skeleton);

if (skeleton.nonOsmObject) {
coordsClicked(map, coords, setFeature);
return;
}
if (skeleton.nonOsmObject) {
coordsClicked(map, coords, setFeature);
return;
}

// router wouldnt overwrite the skeleton if same url is already loaded
setFeature((feature) =>
isSameOsmId(feature, skeleton) ? feature : skeleton,
);
setAccessMethod('click');
// router wouldnt overwrite the skeleton if same url is already loaded
setFeature((feature) =>
isSameOsmId(feature, skeleton) ? feature : skeleton,
);

const result = await maptilerFix(features[0], skeleton, features[0].id);
addFeatureCenterToCache(getShortId(skeleton.osmMeta), skeleton.center); // for ways/relations we dont receive center from OSM API
addFeatureCenterToCache(getShortId(result.osmMeta), skeleton.center);
pushFeatureToRouter(result);
},
}),
);
const result = await maptilerFix(features[0], skeleton, features[0].id);
addFeatureCenterToCache(getShortId(skeleton.osmMeta), skeleton.center); // for ways/relations we dont receive center from OSM API
addFeatureCenterToCache(getShortId(result.osmMeta), skeleton.center);
pushFeatureToRouter(result);
},
}));
3 changes: 3 additions & 0 deletions src/components/Map/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,6 @@ const isWebglSupported = () => {
};

export const webglSupported = isBrowser() ? isWebglSupported() : true;

export const supports3d = (activeLayers: string[]) =>
activeLayers.includes('basic') || activeLayers.includes('outdoor');
3 changes: 2 additions & 1 deletion src/components/SearchBox/AutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
autocompleteRef,
setOverpassLoading,
}) => {
const { setFeature, setPreview } = useFeatureContext();
const { setFeature, setPreview, setAccessMethod } = useFeatureContext();
const { bbox } = useMapStateContext();
const { showToast } = useSnackbar();
const mapCenter = useMapCenter();
Expand All @@ -93,6 +93,7 @@ export const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
showToast,
setOverpassLoading,
router,
setAccessMethod,
})}
onHighlightChange={onHighlightFactory(setPreview)}
getOptionDisabled={(o) => o.type === 'loader'}
Expand Down
7 changes: 6 additions & 1 deletion src/components/SearchBox/onSelectedFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
StarOption,
} from './types';
import { osmOptionSelected } from './options/openstreetmap';
import { AccessMethod } from '../utils/FeatureContext';

const overpassOptionSelected = (
option: OverpassOption | PresetOption,
Expand Down Expand Up @@ -67,6 +68,7 @@ type SetFeature = (feature: Feature | null) => void;
const geocoderOptionSelected = (
option: GeocoderOption,
setFeature: SetFeature,
setAccessMethod: (accessMethod: AccessMethod) => void,
) => {
if (!option?.geocoder.geometry?.coordinates) return;

Expand All @@ -75,6 +77,7 @@ const geocoderOptionSelected = (

addFeatureCenterToCache(getShortId(skeleton.osmMeta), skeleton.center);

setAccessMethod('search');
setFeature(skeleton);
fitBounds(option);
Router.push(`/${getUrlOsmId(skeleton.osmMeta)}`);
Expand All @@ -87,6 +90,7 @@ type OnSelectedFactoryProps = {
showToast: ShowToast;
setOverpassLoading: React.Dispatch<React.SetStateAction<boolean>>;
router: NextRouter;
setAccessMethod: (accessMethod: AccessMethod) => void;
};

export const onSelectedFactory =
Expand All @@ -97,6 +101,7 @@ export const onSelectedFactory =
showToast,
setOverpassLoading,
router,
setAccessMethod,
}: OnSelectedFactoryProps) =>
(_: never, option: Option) => {
setPreview(null); // it could be stuck from onHighlight
Expand All @@ -110,7 +115,7 @@ export const onSelectedFactory =
overpassOptionSelected(option, setOverpassLoading, bbox, showToast);
break;
case 'geocoder':
geocoderOptionSelected(option, setFeature);
geocoderOptionSelected(option, setFeature, setAccessMethod);
break;
case 'osm':
osmOptionSelected(option, router);
Expand Down
14 changes: 13 additions & 1 deletion src/components/utils/FeatureContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { useBoolState } from '../helpers';
import { publishDbgObject } from '../../utils';
import { setLastFeature } from '../../services/lastFeatureStorage';

export type AccessMethod = 'click' | 'search' | 'link';

export type FeatureContextType = {
feature: Feature | null;
featureShown: boolean;
Expand All @@ -24,6 +26,8 @@ export type FeatureContextType = {
persistShowHomepage: () => void;
preview: Feature | null;
setPreview: (feature: Feature | null) => void;
accessMethod: AccessMethod | null;
setAccessMethod: (method: AccessMethod | null) => void;
};

export const FeatureContext = createContext<FeatureContextType>(undefined);
Expand All @@ -41,6 +45,7 @@ export const FeatureProvider = ({
}: Props) => {
const [preview, setPreview] = useState<Feature>(null);
const [feature, setFeature] = useState<Feature>(featureFromRouter);
const [accessMethod, setAccessMethod] = useState<AccessMethod | null>(null);
const featureShown = feature != null;

useEffect(() => {
Expand Down Expand Up @@ -72,14 +77,21 @@ export const FeatureProvider = ({
const value: FeatureContextType = {
feature,
featureShown,
setFeature,
setFeature: (feature) => {
if (!feature) {
setAccessMethod(null);
}
return setFeature(feature);
},
homepageShown,
showHomepage,
hideHomepage,
persistShowHomepage,
persistHideHomepage,
preview,
setPreview,
accessMethod,
setAccessMethod,
};
return (
<FeatureContext.Provider value={value}>{children}</FeatureContext.Provider>
Expand Down
Loading
Loading