Setting language option in Leaflet.Routing.Control - javascript

UPDATED:
I am trying to translate the leaflet routing component with language:'sp'
but it does not work for me.
const createRoutingControl = () => {
L.Routing.control({
router: L.Routing.mapbox(config.features.routing.key),
plan: new (L.Routing.Plan.extend({
createGeocoders: function () {
let container = L.Routing.Plan.prototype.createGeocoders.call(this)
let reverseRoute = createButton('↑↓', container)
let copyToClipboard = createButton('5', container, true)
return container
}
}))([], {
geocoder: geocoder,
language: 'sp'
}),
units: config.features.routing.units,
showAlternatives: true,
autoRoute: true,
routeWhileDragging: true,
}).addTo(map)
}
With " language:'sp' " the form is traslated but not the instruccions.
I know I have to use formatter but I tried to put it in routing.control, routing.plan... (and more places only to test it) and it does not work (the map is not displayed)

The response from #IvanSanchez is almost correct: Control does indeed not have a language option, but several other classes have (not sure this is even properly documented, sorry).
Having said that, as a default, the Control class passes on any option you give it when it instantiates its child components, so passing language as an option to Control will also pass it to the default formatter, the router, etc. The exception is when you provide child components that you instantiate yourself, like the Plan in your example: for that case, you need to explicitly provide the options (like you already do).
So I would assume this code should fix the issue:
const createRoutingControl = () => {
L.Routing.control({
router: L.Routing.mapbox(config.features.routing.key),
plan: new (L.Routing.Plan.extend({
createGeocoders: function () {
let container = L.Routing.Plan.prototype.createGeocoders.call(this)
let reverseRoute = createButton('↑↓', container)
let copyToClipboard = createButton('5', container, true)
return container
}
}))([], {
geocoder: geocoder,
language: 'sp'
}),
units: config.features.routing.units,
showAlternatives: true,
autoRoute: true,
routeWhileDragging: true,
language: 'sp'
}).addTo(map)

I'm sorry to dig up this topic, but I can not translate the instructions in french...
I set the language option 'fr' directly in L.Routing.control and also in the formatter...
I try to debug the lines where the localization is done, and I see my problem at line 16353 of leaflet-routing-machine.js :
formatInstruction: function(instr, i) {
if (instr.text === undefined) {
return this.capitalize(L.Util.template(this._getInstructionTemplate(instr, i),
.....
})));
} else {
return instr.text;
}
where instr.text is already initialized...
If I reset instr.text to "undefined" at debug time, the instruction is well translated...
Do you have any idea of my problem?
My code is below:
$script(['https://unpkg.com/leaflet/dist/leaflet-src.js'], () =>{
$script(['https://unpkg.com/leaflet-routing-machine/dist/leaflet-routing-machine.js'], () => {
this.map = L.map(element);
L.tileLayer('//{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png', {
attribution: 'Map data © OpenStreetMap - rendu OSM France ',
maxZoom: 18
}).addTo(this.map);
this.map = L.map(element);
L.tileLayer('//{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png', {
attribution: 'Map data © OpenStreetMap - rendu OSM France ',
maxZoom: 18
}).addTo(this.map);
let control1 = L.Routing.control(
{
router: L.routing.mapbox(this.key.MapboxKey),
language: 'fr',
formatter: new L.Routing.Formatter({
language: 'fr'
}),
waypoints: [
L.latLng(47.928927, 7.538860),
L.latLng(48.044444, 7.299279),
L.latLng(47.857503, 6.821690),
L.latLng(47.506390, 6.996181),
L.latLng(47.586881, 7.25652)
],
routeWhileDragging: true
})
.on('routingerror', function(e) {
try {
this.map.getCenter();
} catch (e) {
this.map.fitBounds(L.latLngBounds(control1.getWaypoints().map(function(wp) { return wp.latLng; })));
}
handleError(e);
})
.addTo(this.map);
L.Routing.errorControl(control1).addTo(this.map);
====================================================================
As I found the solution in the meantime, I give it away because it seems not to be documented :
I have to add the language option as parameter of the mapbox function, when creating the "router" :
L.routing.mapbox(this.key.MapboxKey, {language: 'fr'}),

This is a case of RTFM.
If you look a bit closer at the API documentation for Leaflet Routing Machine, you'll notice that L.Routing.Control does not have a language option, and that the language option only applies to instances of L.Routing.Formatter, and that a L.Routing.Control can take a formatter option which can hold an instance of L.Routing.Formatter. So putting everything together...
L.Routing.control({
router: L.Routing.mapbox(config.features.routing.key),
formatter: L.Routing.formatter({
language: 'sp'
units: config.features.routing.units,
}),
showAlternatives: true,
autoRoute: true,
// ... etc
},
(P.S.: "prop" is reactjs slang, and that word does not apply here)

Related

An object property is not being defined, but it sometimes is defined when I refresh the page or later in the code

Hey there I have this piece of code that gets the users geolocation and stores that in a location property of a Data class, simple enough.
When I use the default constructor for this function I want it to call the function
_initLocation()
To define the _location property.
This is the code for the object:
constructor(){
this._initLocation();
this._dataStore = new Map();
}
_initLocation(){
if(navigator.geolocation){
navigator.geolocation.getCurrentPosition((position) => {
const { latitude, longitude } = position.coords;
this._location = new Array(latitude, longitude);
}, () => alert('location not found'))
}
}
get location(){
return this._location;
}
get dataStore(){
return this._dataStore;
}
icon = L.icon({
iconUrl: 'icon.png',
iconSize: [100, 100],
iconAnchor: [50, 100],
popupAnchor: [0, -100],
});
}
When I use this same code outside of the Object, it works perfectly fine, but whenever I try to use console.log on the newly created object I get:
Object { icon: {…}, _dataStore: Map(0) }
​
_dataStore: Map(0)
​
icon: Object { options: {…}, _initHooksCalled: true }
​
<prototype>: Object { … }
So there is no location property on this object, but later on if I console.log the object that property will later exist. It also sometimes appears on refresh. It's ruining code later in the project because that location data is required but undefined. It's clear I need some delay or lag? I've tried DOMContentLoaded. But I'm also uncertain as to why the alert popup isn't showing if it can't get the current location?
Also frustratingly when I console.log the location value in the _initLocation() function I get the correct data, but if I console.log the _location property I get 'undefined'??
Any help would be greatly appreciated.
Edit, here is how I'm using that object:
I instantiate the object then I used it in an object named Renderer, by assigning it to _maptyData property. I then use that property in these functions:
createMap(){
this.map = L.map('map')
.setView(this.maptyData.location, 13);
L.tileLayer('https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'})
.addTo(this.map);
}
createMarker(atLocation = maptyData.location){
if(atLocation === maptyData.location) {
console.log(maptyData._location);
this._locMarker = L.marker(atLocation, {icon: maptyData.icon})
.addTo(this.map)
.bindPopup('Your current location')
.openPopup();
console.log(this._locMarker);
} else {
this.currentMarker = L.marker(atLocation, {icon: maptyData.icon})
.addTo(this.map)
.bindPopup('Please enter event information!')
.openPopup();
return this.currentMarker;
}
I resolved this problem, I'm new to javascript and later realised the problem is because the code needs to be async, I learnt about promises and implemented them to fix this.

Google Maps markerClusterer: how to combine Interface Renderer and GridOptions gridSize in configuration object?

I'm currently trying to implement Google Maps JavaScript MarkerClusterer on a website.
I'm using Interface Renderer to override a couple of default configuration parameters.
let renderer = {
render: ({ count, position }) =>
new google.maps.Marker({
label: {
text: String(count),
color: "transparent",
},
position,
icon: {
url: mapOperator.imageBasePath + "pin-cluster-1.png",
},
// adjust zIndex to be above other markers
zIndex: Number(google.maps.Marker.MAX_ZINDEX) + count,
}),
};
let config = { map, markers, renderer };
let cluster = new markerClusterer.MarkerClusterer(config);
Please note that I'm adding markerClusterer plugin via unpkg. So I must declare the cluster this way: let cluster = new markerClusterer.MarkerClusterer(config); (as stated in the "Install" section of the official documentation) (see last line of my above code bit).
This code works great, but I'd also like to override gridSize property from Interface GridOptions in order (well, I hope... I'm not even sure this option will give me what I expect; I'm not english native and given description is not totally clear to me...) to get bigger clusters and less individual markers on my map.
I'm struggling for a couple of reasons:
I'm not familiar with they way the code has to be set up,
the documentation is EMPTY and there's NO supplied example code on how to achieve what I want,
can't find any help on stack overflow, tutorials (blogs, social networks, ...). Really, all I could find were 100k of obsolete ways to do it but that doesn't work when you use Interface Renderer. Here's an example:
var markerClusterer = new MarkerClusterer(map, markers, {
gridSize: 10,
maxZoom: 9, // maxZoom set when clustering will stop
imagePath: 'https://cdn.rawgit.com/googlemaps/js-marker-clusterer/gh-pages/images/m'
});
So I tried several things but nothing is working:
let config = { map, markers, renderer, GridOptions: { gridSize: 10 } };
let renderer = {
render: ({ count, position }) =>
new google.maps.Marker({
label: {
text: String(count),
color: "transparent",
},
position,
icon: {
url: mapOperator.imageBasePath + "pin-cluster-1.png",
},
// adjust zIndex to be above other markers
zIndex: Number(google.maps.Marker.MAX_ZINDEX) + count,
gridSize: 10,
}),
};
let renderer = {
render: ({ count, position }) =>
new google.maps.Marker({
label: {
text: String(count),
color: "transparent",
},
position,
icon: {
url: mapOperator.imageBasePath + "pin-cluster-1.png",
},
// adjust zIndex to be above other markers
zIndex: Number(google.maps.Marker.MAX_ZINDEX) + count,
}),
GridOptions: ({ gridSize = 10 }),
// gridSize: 10,
};
Can anyone help me? Thanks!
Finally:
let renderer = {
render: ({ count, position }) =>
new google.maps.Marker({
label: {
text: String(count),
color: "transparent",
},
position,
icon: {
url: mapOperator.imageBasePath + "pin-cluster-1.png",
},
// adjust zIndex to be above other markers
zIndex: Number(google.maps.Marker.MAX_ZINDEX) + count,
}),
};
let algorithm = new markerClusterer.GridAlgorithm({gridSize: 60});
let config = { map, markers, renderer, algorithm };
let cluster = new markerClusterer.MarkerClusterer(config);
And also, Yes, gridSize property does generate bigger clusters and less individual markers on map if you increase its value (default: 40).

Map layers do not render again past initial load

I am using VueMapbox (0.4.1) to utilize Mapbox GL in a Vue project.
<template>
<MglMap
:accessToken="accessToken"
:mapStyle.sync="mapStyle"
:repaint="true"
#load="onMapLoaded">
<MglMarker
:coordinates="someCoordinates"
class="map-marker-wrapper">
<div
slot="marker"
class="map-marker">
</div>
</MglMarker>
</MglMap>
</template>
<script>
import Mapbox from 'mapbox-gl'
import { MglMap, MglMarker } from 'vue-mapbox'
import * as MAP from '#/constants/map'
// Vue-Mapbox documentation: https://soal.github.io/vue-mapbox/guide/basemap.html#adding-map-component
export default {
name: 'Map',
components: {
MglMap,
MglMarker
},
props: {
someCoordinates: {
type: Array,
required: true
},
darkMode: {
type: Boolean,
required: true
}
},
data () {
return {
accessToken: MAP.ACCESS_TOKEN,
mapbox: null,
map: null,
actionsDispatcher: null
}
},
computed: {
mapStyle () {
return this.darkMode ? MAP.COLOR_PROFILES.DARK : MAP.COLOR_PROFILES.LIGHT
}
},
created () {
this.mapbox = Mapbox
},
methods: {
async onMapLoaded (event) {
this.map = event.map
this.actionsDispatcher = event.component.actions
await this.actionsDispatcher.flyTo({
center: this.someCoordinates
})
}
}
}
</script>
On the first load, everything works as expected:
But if I move to a different pseudo-route (say from /#/Map to /#/Profile and back), some of the map layers specified by my mapStyle (roads, city names,..) are not rendered anymore (default layers instead). The map also stops honoring any change of the mapStyle url, even when I specify mapStyle.sync in my template.
If I hit the browser's reload button it loads the layers as expected as the entire app is reloaded from scratch, but I unfortunately cannot afford to do this by default.
Any ideas are greatly appreciated.
I found a solution, in the example of vue-mapbox, the variable map (this.map) is set by "event.map" which causes an error because the card is not refreshed afterwards.
In my case i just remove that (this.map = event.map) in my onMapLoaded function and this is great.
Have a good day.
Despite I don’t know the syntax of Vue.js, the problem you are facing is that you are creating your layers in map.on('load', ... which is an event that happens only once, so when the style change happens, all the layers of the map style (including the ones created by custom code) are removed.
If you want to recreate your layers on style change, you have to do it in the event map.on('style.load', ..., but as said, I don’t see in your vue.js code where that is being done. If you share the part of the code where vue.js is invoking the methods it’ll be easier to help you

Mapbox markers flashing when using `icon-allow-overlap`

I have a map that's using mapboxgl-js to hide or show map markers based on some criteria.
Hiding the markers is working as expected, but when I want the markers to show again they flash for some milliseconds then disappear again while the map hides labels (street names etc.) on the underlying layer before they show up again.
See this video: https://streamable.com/debcp
See this codepen: https://codepen.io/jakobfuchs/details/VRRgJO
I came to the conclusion that this is caused by setting 'icon-allow-overlap': true on the marker symbol layer.
Any suggestions how I can keep that setting and avoid the flashing?
The strange thing is that this does not happen 100% of the time but ~95% of the time.
Code samples:
Marker layer:
map.addLayer({
id: 'property-layer',
type: 'symbol',
source: 'properties',
filter: ['!has', 'point_count'],
layout: {
'symbol-placement': 'point',
'icon-image': 'marker',
'icon-size': 1,
'icon-anchor': 'bottom',
'icon-allow-overlap': true,
}
});
Filter code:
const filterToggle = document.getElementById('filterToggle');
filterToggle.addEventListener('change', function(e) {
if (openPopup) {
openPopup.remove();
openPopup = '';
}
let properties;
if (this.checked) {
properties = {
type: "FeatureCollection",
features: features.features.filter((property) => property.properties.availability === 'Available')
}
} else {
properties = features;
}
map.getSource('properties').setData(properties);
});
I have faced with the same issue and my solution is use the icon-ignore-placement instead of icon-allow-overlap, and it's still not have any issues
You can find the document here: https://docs.mapbox.com/mapbox-gl-js/style-spec/#layout-symbol-icon-ignore-placement
Hope this will help, thanks!

Vue.js: Target other div than the bound one within vue object

I'm using Vue and Leaflet for displaying polygons (zones) on a map and display appropriate information (messages) about the specific polygons after clicking on them on the map. The div, where I render the messages in, has the id "#messagearea" and is bound to the "el" object. To display the appropriate messages, I am dependant on the "Zone-id".
Now I also want to display information into another div with a different id. I am also dependant on the "Zone-id" here, so I would like to do this in the same Vue. If I would create another Vue, I would have to render the Leaflet map again to write another polygon.on('click',...) function, which displays appropriate information for the polygons. What is the most elegant and/or easiest way to realize this?
Here my vue object:
var mapVue = new Vue({
el: '#messagearea',
data: {
function() {
return {
map: false
};
},
zones: [],
messages: [],
},
ready: function () {
this.map = L.map('map').setView([51.959, 7.623], 14);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(this.map);
this.$http.get('/api/zones', function (data) {
this.$set('zones', data);
for (var i = 0; i < this.zones['Zones'].length; i++) {
polygon = L.polygon(
this.zones['Zones'][i]['Geometry']['Coordinates']).addTo(this.map);
polygon.bindPopup(this.zones['Zones'][i]['Name']);
polygon.on('click', messageCallback(i))
// HERE I WOULD LIKE TO ADD THE FUNCTION FOR THE OTHER DIV
}
function messageCallback(i) {
return function () {
mapVue.getMessages(mapVue.zones['Zones'][i]['Zone-id']);
}
}
});
},
methods:
{
getMessages: function (id) {
this.$http.get('/api/messages?zone=' + id, function (data) {
console.log("messages called");
this.$set('messages', data['Messages']);
});
}
}
})
I solved this issue by making use of Vue.component(), the vue.$dispatch() and vue.$broadcast() functions. I just dispatched the zone id to a parent component and then delivered it with the broadcast function to all child components, which are in need of the zone id. Displaying the appropriate messages was no problem then anymore.

Categories

Resources