mirror of
https://github.com/HSLdevcom/digitransit-ui
synced 2025-07-27 15:05:15 +02:00
310 lines
8.5 KiB
JavaScript
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 };
|
|
},
|
|
);
|