digitransit-ui/app/component/map/SelectFromMap.js
2025-05-27 18:27:46 +03:00

310 lines
8.5 KiB
JavaScript

import PropTypes from 'prop-types';
import React from 'react';
import get from 'lodash/get';
import { matchShape } from 'found';
import { intlShape } from 'react-intl';
import connectToStores from 'fluxible-addons-react/connectToStores';
import getLabel from '@digitransit-search-util/digitransit-search-util-get-label';
import { configShape } from '../../util/shapes';
import LocationMarker from './LocationMarker';
import MapWithTracking from './MapWithTracking';
import { otpToLocation } from '../../util/otpStrings';
import { getJson } from '../../util/xhrPromise';
import { LightenDarkenColor } from '../../util/colorUtils';
import { mapLayerShape } from '../../store/MapLayerStore';
import withBreakpoint from '../../util/withBreakpoint';
import LocationMarkerWithPermanentTooltip from './LocationMarkerWithPermanentTooltip';
import ConfirmLocationFromMapButton from './ConfirmLocationFromMapButton';
const DESKTOP_BREAKPOINT = 'large';
const markLocation = (markerType, position) => {
let type;
if (markerType === 'origin') {
type = 'from';
} else if (markerType === 'destination') {
type = 'to';
} else {
type = markerType;
}
if (position) {
let newPosition;
if (decodeURIComponent(position).indexOf('::') !== -1) {
newPosition = otpToLocation(decodeURIComponent(position));
} else {
newPosition = position;
}
return <LocationMarker key="location" position={newPosition} type={type} />;
}
return null;
};
class SelectFromMap extends React.Component {
static contextTypes = {
match: matchShape,
config: configShape,
intl: intlShape,
};
static propTypes = {
breakpoint: PropTypes.string,
language: PropTypes.string,
type: PropTypes.string.isRequired,
onConfirm: PropTypes.func.isRequired,
mapLayers: mapLayerShape.isRequired,
};
static defaultProps = {
breakpoint: undefined,
language: undefined,
};
constructor(props) {
super(props);
this.state = {};
}
setMapElementRef = element => {
this.map = get(element, 'leafletElement', null);
};
setAddress = (lat, lon) => {
const { intl } = this.context;
const searchParams = {
'point.lat': lat,
'point.lon': lon,
'boundary.circle.radius': 0.1, // 100m
lang: this.props.language,
size: 1,
layers: 'address',
zones: 1,
};
if (this.context.config.searchParams['boundary.country']) {
searchParams['boundary.country'] =
this.context.config.searchParams['boundary.country'];
}
getJson(this.context.config.URL.PELIAS_REVERSE_GEOCODER, searchParams).then(
data => {
if (data.features != null && data.features.length > 0) {
const match = data.features[0].properties;
this.setState(prevState => ({
mapCenter: {
...prevState.mapCenter,
address: getLabel(match),
lat,
lon,
onlyCoordinates: false,
},
}));
} else {
this.setState(prevState => ({
mapCenter: {
...prevState.mapCenter,
address: intl.formatMessage({
id: 'location-from-map',
defaultMessage: 'Selected location',
}), // + ', ' + JSON.stringify(centerOfMap.lat).match(/[0-9]{1,3}.[0-9]{6}/) + ' ' + JSON.stringify(centerOfMap.lng).match(/[0-9]{1,3}.[0-9]{6}/),
lat,
lon,
onlyCoordinates: true,
},
}));
}
},
() => {
this.setState({
mapCenter: {
address: intl.formatMessage({
id: 'location-from-map',
defaultMessage: 'Selected location',
}), // + ', ' + JSON.stringify(centerOfMap.lat).match(/[0-9]{1,3}.[0-9]{6}/) + ' ' + JSON.stringify(centerOfMap.lng).match(/[0-9]{1,3}.[0-9]{6}/),
lat,
lon,
onlyCoordinates: true,
},
});
},
);
};
onClick = e => {
const clickedDiv = e.originalEvent.target;
if (clickedDiv.tagName === 'BUTTON') {
return;
}
this.setState({
mapCenter: {
address: '',
lat: e.latlng.lat,
lon: e.latlng.lng,
},
});
this.setAddress(e.latlng.lat, e.latlng.lng);
};
setMapLocation = () => {
if (!this.map) {
return;
}
const centerOfMap = this.map.getCenter();
if (
this.state.mapCenter &&
this.state.mapCenter.lat === centerOfMap.lat &&
this.state.mapCenter.lon === centerOfMap.lng
) {
return;
}
this.setAddress(centerOfMap.lat, centerOfMap.lng);
};
createAddress = (address, position) => {
if (address !== '') {
const newAddress = address.split(', ');
let strippedAddress = newAddress[0];
if (!this.state.mapCenter.onlyCoordinates) {
strippedAddress = `${strippedAddress}, ${newAddress[1]}`;
}
strippedAddress = `${strippedAddress}::${JSON.stringify(
position.lat,
)},${JSON.stringify(position.lon)}`;
return strippedAddress;
}
return '';
};
confirmButton = (isEnabled, mapCenter, positionSelectingFromMap) => {
const { intl } = this.context;
return (
<ConfirmLocationFromMapButton
isEnabled={!!isEnabled}
address={
isEnabled
? this.createAddress(mapCenter.address, positionSelectingFromMap)
: undefined
}
title={intl.formatMessage({
id: 'location-from-map-confirm',
defaultMessage: 'Confirm selection',
})}
type={this.props.type}
onConfirm={this.props.onConfirm}
color={this.context.config.colors.primary}
hoverColor={
this.context.config.colors.hover ||
LightenDarkenColor(this.context.config.colors.primary, -20)
}
/>
);
};
render() {
const { config, match } = this.context;
const { type } = this.props;
const { mapCenter } = this.state;
const defaultLocation = config.defaultEndpoint;
const isDesktop = this.props.breakpoint === DESKTOP_BREAKPOINT;
const leafletObjs = [];
if (!mapCenter && type === 'origin' && !isDesktop) {
leafletObjs.push(
<LocationMarker
key="fromMarker"
position={defaultLocation}
type="from"
disabled
/>,
);
}
if (!mapCenter && type === 'destination' && !isDesktop) {
leafletObjs.push(
<LocationMarker
key="toMarker"
position={defaultLocation}
type="to"
disabled
/>,
);
}
if (match.location.query && match.location.query.intermediatePlaces) {
if (Array.isArray(match.location.query.intermediatePlaces)) {
match.location.query.intermediatePlaces
.map(otpToLocation)
.forEach((markerLocation, i) => {
leafletObjs.push(
<LocationMarker
key={`via_${i}`} // eslint-disable-line react/no-array-index-key
position={markerLocation}
/>,
);
});
} else {
leafletObjs.push(
<LocationMarker
key="via"
position={otpToLocation(match.location.query.intermediatePlaces)}
/>,
);
}
}
const positionSelectingFromMap = mapCenter || defaultLocation;
if (!mapCenter) {
leafletObjs.push(this.confirmButton(false));
} else {
leafletObjs.push(markLocation(this.props.type, positionSelectingFromMap));
leafletObjs.push(
<LocationMarkerWithPermanentTooltip
position={positionSelectingFromMap}
text={mapCenter.address}
/>,
);
leafletObjs.push(
this.confirmButton(true, mapCenter, positionSelectingFromMap),
);
}
const eventHooks = {};
if (isDesktop) {
eventHooks.leafletEvents = {
onClick: this.onClick,
};
} else {
eventHooks.onEndNavigation = this.setMapLocation;
}
return (
<MapWithTracking
className="select-from-map full"
leafletObjs={leafletObjs}
lat={defaultLocation.lat}
lon={defaultLocation.lon}
zoom={12}
mapLayers={this.props.mapLayers}
locationPopup="none"
mapRef={this.setMapElementRef}
{...eventHooks}
/>
);
}
}
export default connectToStores(
withBreakpoint(SelectFromMap),
['MapLayerStore'],
({ getStore }) => {
const mapLayers = getStore('MapLayerStore').getMapLayers({
notThese: ['vehicles'],
});
return { mapLayers };
},
);