Here it is: http://www.liedman.net/leaflet-routing-machine/tutorials/basic-usage/
L.Routing.control({
waypoints: [
L.latLng(57.74, 11.94),
L.latLng(57.6792, 11.949)
],
routeWhileDragging: true
}).addTo(map);
Could you please help me create a react-leaflet component for it? Here is what I've tried:
import Routing from 'components/search/routing';
...
<Map center={position} zoom={13}>
<TileLayer url={`http://tile.geofabrik.de/${GEOFABRIK_API_KEY}/{z}/{x}/{y}.png',`} />
<Marker position={position} />
<Routing from={[57.74, 11.94]} to={[57.6792, 11.949]} />
</Map>
components/search/routing.jsx:
import Routing from 'leaflet-routing-machine';
import {MapLayer} from 'react-leaflet';
export default class RoutingMachine extends MapLayer {
componentWillMount() {
super.componentWillMount();
const {map, from, to} = this.props;
this.leafletElement = Routing.control({
waypoints: [
this.leafletElement.latLng(from[0], from[1]),
this.leafletElement.latLng(to[0], to[1]),
],
}).addTo(map);
}
render() {
return null;
}
}
I used Popup.js as an example, as it seems the most appropriate to me. But in this case this is undefined, I don't have access to leafletElement.
Sorry for the newbee question, I'm new to React.
Thank you!
I've found the way it works:
import {MapLayer} from 'react-leaflet';
import L from 'leaflet';
import 'leaflet-routing-machine';
export default class RoutingMachine extends MapLayer {
componentWillMount() {
super.componentWillMount();
const {map, from, to} = this.props;
this.leafletElement = L.Routing.control({
position: 'topleft',
waypoints: [
L.latLng(from[0], from[1]),
L.latLng(to[0], to[1]),
],
}).addTo(map);
}
render() {
return null;
}
}
Is it right?
Unfortunately it doesn't work fully. The markers and the path are displayed, but the routing control (this one: http://take.ms/qhUEC) is empty. Any ideas?
I've accidentally figured it out myself: when Map component's zoom option is provided, it just fails silently, without errors in the console. Otherwise (without zoom option) it works well.
So in my case it should be:
<Map center={position}>
<TileLayer url={`http://tile.geofabrik.de/${GEOFABRIK_API_KEY}/{z}/{x}/{y}.png',`} />
<Marker position={position} />
<Routing from={[57.74, 11.94]} to={[57.6792, 11.949]} />
</Map>
And the map itself doesn't work without providing zoom option, which is weird because it isn't a required option.
So to be clear, this works:
<Map center={position} zoom={10}>
<TileLayer url={`http://tile.geofabrik.de/${GEOFABRIK_API_KEY}/{z}/{x}/{y}.png',`} />
<Marker position={position} />
</Map>
And this doesn't:
<Map center={position}>
<TileLayer url={`http://tile.geofabrik.de/${GEOFABRIK_API_KEY}/{z}/{x}/{y}.png',`} />
<Marker position={position} />
</Map>
You should make it required or add a default value.
Hi,
Please refer to the documentation for creating custom components.
I don't know leaflet-routing-machine but it seems you're on the right path: you basically need to instantiate the Leaflet element and attach it to the provided map property. Depending on your needs, you might want to handle specific properties in your component.
Regarding the center and zoom properties, they are not required as they do not prevent the creation of the map. This lib simply provides bindings to Leaflet, it is not meant to change its behavior.
You are not right, zoom property prevents creation of the map.
This configuration:
<Map center={position}>
<TileLayer url={`http://tile.geofabrik.de/${GEOFABRIK_API_KEY}/{z}/{x}/{y}.png',`} />
</Map>
doesn't work, it returns a blank page without any errors in the console while:
<Map center={position} zoom={13}>
<TileLayer url={`http://tile.geofabrik.de/${GEOFABRIK_API_KEY}/{z}/{x}/{y}.png',`} />
</Map>
works well.
And this is a problem for me, because leaflet-routing-machine doesn't work if zoom is provided.
Any ideas?
Please refer to Leaflet's documentation, the center and zoom are not required to instantiate the map, though obviously they are to render anything.
Again, his library is a simple wrapper for Leaflet. If you have issues with plugins that work with Leaflet but not with this lib, please post them here, but in this case your issue seems to be with leaflet-routing-machine.
My question was not about the plugin. I wonder why this library doesn't work for me without zoom parameter while you say it works.
Hi @web2style did you get leaflet-routing-machine working with react-leaflet?
Hi, @eelcocramer. Yes, I did. I'm using condition and render separate instances of <Map /> with unique keys. Here's an example:
render() {
const {position, fromLat, fromLon, toLat, toLon, routingActive, itinerary} = this.state;
return (
!routingActive ?
<Map
key="map"
zoom={13}
center={position}
zoomControl={false}
>
<TileLayer url={`http://tile.geofabrik.de/${GEOFABRIK_API_KEY}/{z}/{x}/{y}.png`} />
<ZoomControl position="bottomright" />
<Marker position={position} />
</Map> :
<div className="map">
<Map
key={`routing-${fromLat}-${fromLon}-${toLat}-${toLon}`}
zoomControl={false}
>
<TileLayer url={`http://tile.geofabrik.de/${GEOFABRIK_API_KEY}/{z}/{x}/{y}.png`} />
<ZoomControl position="bottomright" />
<Routing
coords={{fromLat, fromLon, toLat, toLon}}
itineraryReady={this.initItinerary}
/>
{itinerary ? itinerary.route.instructions.map((item, i) => {
const coordinate = itinerary.route.coordinates[item.index];
const radius = this.state.hoveredPoint === i ? 3 : 2;
const color = this.state.hoveredPoint === i ? '#455a64' : '#4495d2';
return (
<CircleMarker
key={coordinate}
radius={radius}
color={color}
fillColor="white"
opacity={1}
fillOpacity={0.7}
center={coordinate}
onMouseOver={this.onCircleMouseOver.bind(null, i)}
onMouseOut={this.onCircleMouseOut}
/>
);
}) : '' }
</Map>
{this.state.itinerary ?
<Itinerary
itinerary={this.state.itinerary}
map={this.state.map}
close={this.unmountItinerary}
mouseOver={this.onItineraryMouseOver}
hoveredPoint={this.state.hoveredPoint}
/> : ''
}
</div>
);
}
Thanks!
Hi, @web2style. I want to create a react-leaflet component for routing-machine, your problem is solved?
@artemkarpovich Yes, it is solved.
@web2style, could you show how this problem has been solved? I first worked with leaflet and maps. Thank you.
@web2style cloud you please provide a full example? Perhaps in its own repository? I tried to follow your code posted here but didn't quite get it right...
This was a very complex app, no way to extract the leaflet part to show and I can't publish its full source code of course. What exactly you want to achieve and what problems do you face? I can try to help if you describe your use-case in detail.
My environment is ES6 + babel + webpack
The map-component looks like this:
import React from 'react'
import L from 'leaflet'
import {Map, Marker, Popup, TileLayer} from 'react-leaflet'
import 'leaflet/dist/leaflet.css'
import 'leaflet-routing-machine/dist/leaflet-routing-machine.css'
import Routing from './components/routing'
export default React.createClass({
getInitialState () {
return {
lat: 51.505,
lng: -0.09, // London
zoom: 13
}
},
render () {
const {lat, lng, zoom} = this.state
const position = [lat, lng]
let markerIcon = L.icon({
iconUrl: require('../img/leafletjs/marker-icon.png'),
shadowUrl: require('../img/leafletjs/marker-shadow.png'),
iconSize: [25, 41], shadowSize: [41, 41], iconAnchor: [12, 41],
shadowAnchor: [20, 41], popupAnchor: [0, -45]
})
return (
<div>
<Map center={position} zoom={zoom}>
<TileLayer
url='http://{s}.tile.osm.org/{z}/{x}/{y}.png'
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
/>
<Marker position={position} icon={markerIcon}>
<Popup>
<span>A pretty CSS3 popup.<br/>Easily customizable.</span>
</Popup>
</Marker>
<Routing from={[57.74, 11.94]} to={[57.6792, 11.949]} />
</Map>
</div>
)
}
})
And the routing.js component like this (following your example):
import {MapLayer} from 'react-leaflet'
import L from 'leaflet'
import 'leaflet-routing-machine'
export default class RoutingMachine extends MapLayer {
componentWillMount() {
super.componentWillMount()
const {map, from, to} = this.props
this.leafletElement = L.Routing.control({
position: 'topleft',
waypoints: [
L.latLng(from[0], from[1]),
L.latLng(to[0], to[1])
]
}).addTo(map)
}
render() {
return null
}
}
This first results in an error:

and after ignoring the error results in a map that looks like this:

What am I missing here? I don't understand why the start/end marker icons are missing as well...
The <Marker> in London is visible btw...
@Benvorth I'm pretty sure I had the problem with markers, but to be honest I don't remember how I solved it (this was more than a year ago). So I'll publish here the source code of all the files related to react-leaflet, I hope this can help you.
// map-search-result.js: main page of map search and routing
import React, {PropTypes} from 'react';
import Component from 'components/pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Map, Marker, TileLayer, ZoomControl } from 'react-leaflet';
import Routing from 'components/search/routing';
import { GEOFABRIK_API_KEY } from 'constants/search';
import * as searchActions from 'actions/search';
import { connect } from 'react-redux';
import { pushState } from 'redux-router';
import {isEmpty} from 'lodash';
import Itinerary from 'components/search/itinerary';
@connect(
state => ({
routingCoords: state.search.get('routingCoords'),
router: state.router.location,
}),
{
...searchActions,
pushState,
}
)
export default class MapSearchResult extends Component {
static propTypes = {
item: ImmutablePropTypes.map,
results: ImmutablePropTypes.map,
routingCoords: PropTypes.object,
router: PropTypes.object,
fromLat: PropTypes.number,
fromLon: PropTypes.number,
toLat: PropTypes.number,
toLon: PropTypes.number,
}
constructor() {
super();
this.state = {
position: [],
routingActive: false,
fromLat: null,
fromLon: null,
toLat: null,
toLon: null,
itinerary: undefined,
};
}
componentWillMount() {
const {results, router: {query: {s, fromLat, fromLon, toLat, toLon}}} = this.props;
if (s && fromLat && fromLon && toLat && toLon) {
this.setState({
routingActive: true,
fromLat,
fromLon,
toLat,
toLon,
});
}
this.changePosition(results.getIn(['0', 'lat']), results.getIn(['0', 'lon']));
}
componentWillReceiveProps(newProps) {
const {results, routingCoords, router: {query: {s, fromLat, fromLon, toLat, toLon}}} = newProps;
this.changePosition(results.getIn(['0', 'lat']), results.getIn(['0', 'lon']));
if (!isEmpty(routingCoords)) {
this.setState({
fromLat: routingCoords.fromLat,
fromLon: routingCoords.fromLon,
toLat: routingCoords.toLat,
toLon: routingCoords.toLon,
});
}
if (s && fromLat && fromLon && toLat && toLon) {
this.setState({
routingActive: true,
});
} else {
this.setState({
routingActive: false,
});
}
if (
fromLat !== this.props.fromLat ||
fromLon !== this.props.fromLon ||
toLat !== this.props.toLat ||
toLon !== this.props.toLon
) {
this.setState({
fromLat,
fromLon,
toLat,
toLon,
});
}
}
changePosition(lat, lon) {
const positionLat = parseFloat(lat);
const positionLon = parseFloat(lon);
this.setState({position: [positionLat, positionLon]});
}
initItinerary(itinerary) {
this.setState({itinerary});
}
render() {
const {position, fromLat, fromLon, toLat, toLon, routingActive} = this.state;
return (
!routingActive ?
<Map
key="map"
zoom={13}
center={position}
scrollWheelZoom={false}
zoomControl={false}
>
<TileLayer url={`http://tile.geofabrik.de/${GEOFABRIK_API_KEY}/{z}/{x}/{y}.png`} />
<ZoomControl position="bottomright" />
<Marker position={position} />
</Map> :
<div className="map">
<Map
key={`routing-${fromLat}-${fromLon}-${toLat}-${toLon}`}
scrollWheelZoom={false}
zoomControl={false}
>
<TileLayer url={`http://tile.geofabrik.de/${GEOFABRIK_API_KEY}/{z}/{x}/{y}.png`} />
<ZoomControl position="bottomright" />
<Routing
coords={{fromLat, fromLon, toLat, toLon}}
itineraryReady={this.initItinerary}
/>
</Map>
{this.state.itinerary ? <Itinerary data={this.state.itinerary} /> : ''}
</div>
);
}
}
// routing.js
import {PropTypes} from 'react';
import {MapLayer} from 'react-leaflet';
import L from 'leaflet';
import 'leaflet-routing-machine';
import {isEqual} from 'lodash';
export default class RoutingMachine extends MapLayer {
static propTypes = {
itineraryReady: PropTypes.func,
};
componentWillMount() {
super.componentWillMount();
const {coords, map} = this.props;
this.leafletElement = L.Routing.control({
position: 'topleft',
waypoints: [
L.latLng(coords.fromLat, coords.fromLon),
L.latLng(coords.toLat, coords.toLon),
],
collapsible: false,
show: false,
}).addTo(map);
this.leafletElement.on('routeselected', (e) => {
this.props.itineraryReady({e});
console.log(e);
});
}
componentWillReceiveProps(newProps) {
const {coords} = newProps;
if (!isEqual(coords, this.props.coords)) {
this.leafletElement.getPlan().setWaypoints([
L.latLng(coords.fromLat, coords.fromLon),
L.latLng(coords.toLat, coords.toLon),
]);
}
}
render() {
return null;
}
}
import React, {PropTypes} from 'react';
import Component from 'components/pure-component';
import L from 'leaflet';
import 'leaflet-routing-machine';
export default class Itinerary extends Component {
static propTypes = {
data: PropTypes.object,
}
constructor() {
super();
this.formatter = new L.Routing.Formatter({});
}
componentDidMount() {
}
render() {
const {e} = this.props.data;
return (
<div className="itinerary">
<div className="itinerary__title">Route</div>
<div className="itinerary__meta">Length: <strong>{this.formatter.formatDistance(e.route.summary.totalDistance)}</strong>. Time: <strong>{this.formatter.formatTime(e.route.summary.totalTime)}</strong>.</div>
{e.route.instructions.map((item) => {
return (
<div>{this.formatter.formatInstruction(item)}</div>
);
})}
</div>
);
}
}
// routing-controls.js
import React, {PropTypes} from 'react';
import Component from 'components/pure-component';
import {connect} from 'react-redux';
import * as appActions from 'actions/app';
import * as searchActions from 'actions/search';
import {pushState} from 'redux-router';
import Autosuggest from 'components/common/autosuggest';
import {pick} from 'lodash';
import cx from 'classnames';
@connect(
state => ({
router: state.router.location,
}),
{
...appActions,
...searchActions,
pushState,
}
)
export default class RoutingControls extends Component {
static propTypes = {
hideRouting: PropTypes.func.isRequired,
getMapSuggestions: PropTypes.func,
activateRouting: PropTypes.func,
router: PropTypes.object,
pushState: PropTypes.func.isRequired,
}
constructor() {
super();
this.state = {
hidden: false,
togglePushed: false,
};
this.from = '';
this.fromLat = null;
this.fromLon = null;
this.to = '';
this.toLat = null;
this.toLon = null;
}
hideRouting() {
const {pushState, hideRouting, router: {query}} = this.props;
pushState({}, `/search`, {
...pick(query, ['type', 'q']),
});
hideRouting();
}
saveFromCoords(suggestion) {
this.from = suggestion.display_name;
this.fromLat = suggestion.lat;
this.fromLon = suggestion.lon;
}
saveToCoords(suggestion) {
this.to = suggestion.display_name;
this.toLat = suggestion.lat;
this.toLon = suggestion.lon;
}
activateRouting() {
const {pushState, activateRouting, router: {query}} = this.props;
const from = this.from;
const fromLat = this.fromLat;
const fromLon = this.fromLon;
const to = this.to;
const toLat = this.toLat;
const toLon = this.toLon;
pushState({}, `/search`, {
...query,
from,
fromLat,
fromLon,
to,
toLat,
toLon,
});
activateRouting({
fromLat,
fromLon,
toLat,
toLon,
});
}
toggle() {
this.setState({'hidden': !this.state.hidden}, () => setTimeout(() => this.setState({'togglePushed': this.state.hidden}), 1000));
}
reverse() {
const {pushState, router: {query, query: {to, toLat, toLon, from, fromLat, fromLon}}} = this.props;
pushState({}, `/search`, {
...query,
from: to,
fromLat: toLat,
fromLon: toLon,
to: from,
toLat: fromLat,
toLon: fromLon,
});
}
render() {
const {router: {query: {from, to}}} = this.props;
const routingClassName = cx({
'routing': true,
'routing--hidden': this.state.hidden,
});
const routingToggleClassName = cx({
'routing__toggle': true,
' routing__toggle--pushed': this.state.togglePushed,
});
return (
<div className={routingClassName}>
<div
className={routingToggleClassName}
onClick={this.toggle}
>
<i className="routing__toggleIcon zmdi zmdi-chevron-left" />
</div>
<div className="routing__inner">
<div className="routing__heading">
<div className="routing__title">Route planning</div>
<i
className="routing__close zmdi zmdi-close"
onClick={this.hideRouting}
/>
</div>
<div className="routing__body">
<div className="routing__swap">
<span
className="mdl-button mdl-button--fab mdl-button--sm mdl-button--colored mdl-js-button mdl-js-ripple-effect"
onClick={this.reverse}
>
<i className="zmdi zmdi-swap-vertical" />
</span>
</div>
<div className="routing__inputs">
<Autosuggest
type="bordered"
value={from}
inputAttributes={{placeholder: 'From'}}
onSuggestionSelected={this.saveFromCoords}
/>
<Autosuggest
type="bordered"
value={to}
inputAttributes={{placeholder: 'To'}}
onSuggestionSelected={this.saveToCoords}
/>
</div>
</div>
<div className="routing__footer">
<span
className="mdl-button mdl-button--lg mdl-js-button mdl-button--raised mdl-button--colored"
onClick={this.activateRouting}>Go</span>
</div>
</div>
</div>
);
}
}
// package.json
...
"leaflet": "^0.7.5",
"leaflet-routing-machine": "^2.5.0",
"react-leaflet": "^0.9.0",
...
This was just a prototype, so don't pay attention the code is so dirty. First of all it works. It would be refactored if the app survive.
@web2style 馃憤
@Benvorth do you find the solution for this use case? Could you share it with others, please?)
@yantakus using the code you provided, I get the error that map (from `const {coords, map} = this.props) is undefined and so addLayer cannot be called
@lbrouckman you can access the map by ref to it: ref='map', however it is the react ref component and to get your leaflet map you need to call this.refs.map.leafletElement and then you can add layers to your map directly.
Thanks! I'm seeing the route show up now, but not the map and getting this error:

Any ideas?
It will be better, if you show your code here :)
I see you are trying to add routing machine, I added it by this way:
Map.js:import React, { Component } from 'react';
import { Map, TileLayer } from 'react-leaflet';
import { MAPBOX_URL } from 'consts';
import Routing from './Routing';
const position = [53.349183, 83.761164];
class MapContainer extends Component {
render() {
return (
<Map center={position} zoom={5} ref={map => this.map = map}>
<TileLayer
url={MAPBOX_URL}
/>
<Routing map={this.map} />
</Map>
);
}
}
MapContainer.propTypes = {};
MapContainer.defaultProps = {};
export default MapContainer;
Routing.js:import React, { Component } from 'react';
import PropTypes from 'prop-types';
import L from 'leaflet';
import { Popup } from 'react-leaflet';
import 'leaflet-routing-machine';
import 'leaflet-control-geocoder';
import 'leaflet-routing-machine/dist/leaflet-routing-machine.css';
import { MAPBOX_TOKEN, MAPBOX_SERVICE_URL } from 'consts';
class Routing extends Component {
static propTypes = {
map: PropTypes.object,
};
constructor(props) {
super(props);
this.state = {
routingPopUp: null,
};
this.initializeRouting = this.initializeRouting.bind(this);
this.destroyRouting = this.destroyRouting.bind(this);
this.createPopupsHandler = this.createPopupsHandler.bind(this);
this.setRoutingPopUp = this.setRoutingPopUp.bind(this);
}
componentDidUpdate() {
this.initializeRouting();
}
componentWillUnmount() {
this.destroyRouting();
}
initializeRouting() {
if (this.props.map && !this.routing) {
const plan = new L.Routing.Plan([
L.latLng(53.349183, 83.761164),
L.latLng(51.292651, 85.686975)
], {
routeWhileDragging: false,
geocoder: L.Control.Geocoder.nominatim(),
});
this.routing = L.Routing.control({
plan,
serviceUrl: MAPBOX_SERVICE_URL,
router: L.Routing.mapbox(MAPBOX_TOKEN),
});
this.props.map.leafletElement.addControl(this.routing);
L.DomEvent.on(this.props.map.leafletElement, 'click', this.createPopupsHandler);
}
}
destroyRouting() {
if (this.props.map) {
this.props.map.leafletElement.removeControl(this.routing);
L.DomEvent.off(this.props.map.leafletElement, 'click', this.createPopupsHandler);
}
}
createPopupsHandler(e) {
const position = e.latlng;
const startBtnOnClick = () => {
this.routing.spliceWaypoints(0, 1, position);
this.setRoutingPopUp(null);
};
const endBtnOnClick = () => {
this.routing.spliceWaypoints(this.routing.getWaypoints().length - 1, 1, position);
this.setRoutingPopUp(null);
};
const startBtn = <button onClick={startBtnOnClick}>Set begin position</button>;
const endBtn = <button onClick={endBtnOnClick}>Set end position</button>;
const children = (<div>
{startBtn}
{endBtn}
</div>);
const onClose = this.setRoutingPopUp;
this.setRoutingPopUp({ children, position, onClose });
}
setRoutingPopUp(routingPopUp) {
this.setState({ routingPopUp });
}
render() {
const { routingPopUp } = this.state;
if (routingPopUp) return <Popup {...this.state.routingPopUp} />;
return null;
}
}
export default Routing;
I know that it's need to be refactored because it looks very hopeless, but for a now It's enough for me and It is working :)
Most helpful comment
It will be better, if you show your code here :)
I see you are trying to add routing machine, I added it by this way:
Map.js:Routing.js:I know that it's need to be refactored because it looks very hopeless, but for a now It's enough for me and It is working :)