I have created a MapboxGeocoder object in my code and I'm curious if I can access/use it to reverse geocode elsewhere in the code. This, in an effort to get a formatted address from coords.
I created an object like so:
const address = new MapboxGeocoder({
accessToken: mbat,
mapboxgl: mapboxgl,
container: 'geocoder',
countries: "us",
bbox: [-83.726807, 31.784217, -78.013916, 35.415915],
reverseGeocode: true,
}).on("result", function (result) {
console.log(result);
});
I also have the GeolocateControl object in my code and I'm creating it like so:
map.addControl(
new mapboxgl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true
},
// When active the map will receive updates to the device's location as it changes.
trackUserLocation: true,
// Draw an arrow next to the location dot to indicate which direction the device is heading.
showUserHeading: true
}).on('geolocate', (e) => {
console.log(e)
})
)
My question is, is there a way to access the address object within the GeolocateControl event handler to reverse geocode? I am imagining something to this effect:
.on('geolocate', (e) => { console.log(address(e)) })
As long as your geocoder instance is in scope, you should be able to use it in the event handler from GeolocateControl
I am not 100% sure about the data object in the geolocate callback, so inspect it to see how to pull out the lng/lat coordinates.
const geocoder = new MapboxGeocoder(...)
map.addControl(
new mapboxgl.GeolocateControl({
...
}).on('geolocate', (data) => {
const geocoder.query(`${data.coords[0},${data.coords[1]}`)
})
)
Related
I am trying get Isochrone contours when the user clicks on a Marker,
The official Mapbox Documentation uses the built in Mapbox JS methods but I can't make it work with Leaflet JS
Here's what I have
function markerOnClick(lon, lat) {
const urlBase = "https://api.mapbox.com/isochrone/v1/mapbox/";
const profile = "cycling"; // Set the default routing profile
const minutes = 10; // Set the default duration
// Create a function that sets up the Isochrone API query then makes an fetch call
async function getIso() {
const query = await fetch(
`${urlBase}${profile}/${lon},${lat}?contours_minutes=${minutes}&polygons=true&access_token=${accessToken}`,
{ method: "GET" }
);
const data = await query.json();
map.getSource("iso").setData(data);
}
// From the official documentation
map.addSource("iso", {
type: "geojson",
data: {
type: "FeatureCollection",
features: [],
},
});
// I have tried to use the Leaflet geoJSON method to add it to the map
L.geoJSON("iso", {
type: "geojson",
data: {
type: "FeatureCollection",
features: [],
},
}).addTo(map);
// Can't find the substitute for this method in Leaflet
map.addLayer(
{
id: "isoLayer",
type: "fill",
// Use "iso" as the data source for this layer
source: "iso",
layout: {},
paint: {
// The fill color for the layer is set to a light purple
"fill-color": "#5a3fc0",
"fill-opacity": 0.3,
},
},
"poi-label"
);
// Make the API call
getIso();
}
I have tried to use the Leaflet method of adding GeoJSON to the map i.e. L.geoJSON but to no avail the mapbox GL JS methods I am trying to replace are
map.addLayer
map.addSource
any advice would be appreciated
L.geoJSON() expects a GeoJSON data structure, not a string. Do read https://leafletjs.com/reference#geojson .
For your particular case you probably want to do something like
const query = await fetch(`${urlBase}${profile}/${lon},${lat}?contours_minutes=${minutes}&polygons=true&access_token=${accessToken}`,{ method: "GET" });
const data = await query.json();
L.geoJson(data).addTo(map);
hello everyone iam creating app in which user enter counrty name and it will show country data(data fetching from API rescountriesApi) and iam also fetching the latitude and longitude from API and passing to Map component the problem iam getting first time iam entering country name the map showing correct location of the country in map after iam entering the other country it showing still previous country on map and after refreshing the whole page it showing correct in map.
1)Data from Api stored in state countries
{
countries.map(({ latlng }) => {
const [lat, lang] = latlng;
{/* console.log(lat, lang); */ }
return <Maphook lat={lat} lang={lang} />
})
}
2)Maphook code
const Maphook = ({ lat, lang }) => {
const { ref, map, google } = useGoogleMaps(
// Use your own API key, you can get one from Google (https://console.cloud.google.com/google/maps-apis/overview)
"",
// NOTE: even if you change options later
{
center: { lat: lat, lng: lang },
zoom: 3,
},
);
// console.log(map); // instance of created Map object (https://developers.google.com/maps/documentation/javascript/reference/map)
console.log(google); // google API object (easily get google.maps.LatLng or google.maps.Marker or any other Google Maps class)
return <div ref={ref} style={{ width: 400, height: 300 }} />;
};
I have a mapbox map, initialized with the outdoors-v9 style (tried other styles, same behavior). When I add a layer to the map - a marker or a geojson source and zoom the map, the style changes or breaks, I'm not sure which.
This is the map before the zoom
and after the zoom
here are the functions that init the map and add markers
mapboxgl.accessToken = "pk.*******";
buildMap: function() {
const _self = this;
_self.map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/mapbox/outdoors-v9",
center: [-95.712891, 37.09024],
zoom: 3
});
_self.map.on('load', function() {
_self.map.addSource('route', {
'type': 'geojson',
'data': {
"type": "FeatureCollection",
"features": []
}
});
_self.map.addLayer({
'id': 'route',
'source': 'route',
'type': 'line',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#47576A',
'line-width': 3
}
});
});
}
...
const coords = [addressData.longitude, addressData.latitude];
const marker = new mapboxgl.Marker().setLngLat(coords).addTo(this.map);
I am using Vue.js to render the map. Mapbox version v0.45.0
Any help or leads are highly appreciated
Vue data() properties are reactive, they have getters and setters, so, when loading map object or adding vector tiles layer (geojson), Vue tries to add getters & setters to the map & map.layers which causes vue & vue-dev-tools to crash and mess up the map.
If you enable any raster layer, it would work successfully because raster tiles are loaded via the mapbox.css whereas vector tiles being geojson, are added to the map object.
Easiest solution would be to define a non-reactive variable in vue and then re-use it everywhere.
// edit: A correct/recommended way to set non-reactive data: GitHub link
Seems the issue was related with the fact that I'm pushing the marker instance to an observable (a vuejs data field). After pushing the marker instance to an array, the issue disappeared. This comment doesn't really answer why this happens, but hope it helps someone else that might face the same issue
I just faced this issue and realized that I didn't follow the documentation exactly as it was described (jumped right on to coding without reading properly). And the documentation says:
Storing Map object
Take note that it's generally bad idea to add to Vuex or component's
data anything but primitive types and plain objects. Vue adds getters
and setters to every property, so if you add Map object to Vuex store
or component data, it may lead to weird bugs. If you want to store map
object, store it as non-reactive property like in example below.
The problem was that I had also registered "map" inside the "data" object of my Vue component. But in the example code it's not declared in data, only in the "create" function.
https://soal.github.io/vue-mapbox/guide/basemap.html#map-loading
After hours spent on this problem, here is my working solution to access map instance from a store (thanks to https://github.com/vuejs/vue/issues/2637#issuecomment-331913620):
const state = reactive({
map: Object.freeze({ wrapper: /* PUT THE MAP INSTANCE HERE */ });
});
Here is an example with Vue Composition Api:
index.js
import { reactive, computed } from "#vue/composition-api";
export const state = reactive({
map: null
});
export const setMap = (map) => {
state.map = Object.freeze({ wrapper: map});
};
export const getMap = computed(() => state.map.wrapper);
export const initMap = (event) => {
setMap(event.map);
// now you can access to map instance from the "getMap" getter!
getMap.value.addSource("satellite-source", {
type: "raster",
url: "mapbox://mapbox.satellite",
});
getMap.value.addLayer({
id: "satellite-layer",
type: "raster",
source: "satellite-source"
});
};
App.vue
<template>
<MglMap :accessToken="..." :mapStyle="..." #load="onMapLoaded" />
</template>
<script>
import { defineComponent } from "#vue/composition-api";
import { MglMap } from "vue-mapbox";
import { initMap } from "./index.js";
export default defineComponent({
components: {
MglMap
},
setup() {
const onMapLoaded = (event) => {
initMap(event);
}
return { onMapLoaded };
}
});
</script>
I've got the same error.
This happens if you either put the map or the marker on an reactive vue.js instance.
Short and quick answer.
Explanation is similar to #mlb's answer. So you freeze the object to prevent the map from disorientated and for any actions done to the map, call back the data with an extra Object key which in case is 'wrapper'.
<template><MglMap :accessToken="..." :mapStyle="..." #load="onMapLoaded" /></template>
<script>
methods: {
onMapLoaded(event) {
this.mapboxEvent = Object.freeze({wrapper: event.map});
},
panMap(event) {
this.mapboxEvent.wrapper.panTo([lng, lat], {duration: 1000, zoom: 14});
}
}
</script>
The code works fine for two waypoints on an ionic v1 app, but if I add more than two, I get the following error:
Uncaught TypeError: Cannot read property 'lat' of undefined
at o.LatLng.distanceTo (file:///android_asset/www/lib/leaflet/leaflet.js:6:14158)
at e._extendToWaypoints (file:///android_asset/www/lib/leaflet-routing-machine-3.2.5/dist/leaflet-routing-machine.js:3751:18)
at e.initialize (file:///android_asset/www/lib/leaflet-routing-machine-3.2.5/dist/leaflet-routing-machine.js:3699:10)
at new e (file:///android_asset/www/lib/leaflet/leaflet.js:6:2539)
at Object.line (file:///android_asset/www/lib/leaflet-routing-machine-3.2.5/dist/leaflet-routing-machine.js:3329:16)
at e.<anonymous> (file:///android_asset/www/js/services/Maps.js:461:35)
at e.fireEvent (file:///android_asset/www/lib/leaflet/leaflet.js:6:4952)
at e.<anonymous> (file:///android_asset/www/lib/leaflet-routing-machine-3.2.5/dist/leaflet-routing-machine.js:2907:13)
at e._routeDone (file:///android_asset/www/lib/lrm-mapbox/lrm-mapbox.js:289:20)
at e.<anonymous> (file:///android_asset/www/lib/lrm-mapbox/lrm-mapbox.js:248:22)
The strange thing is that this code where working well a few months ago, but suddenly it started to fail. The problematic code is this:
function getRoute() {
var r = L.Routing.control({
waypoints: waypoints,
router: new L.Routing.Mapbox(Config.mapBoxApiKey,
{
serviceUrl: 'https://api.tiles.mapbox.com/v4/directions/',
timeout: 30 * 1000,
profile: 'mapbox.' + tipo
}
),
lineOptions: {
styles: styles
},
fitSelectedRoutes: false,
routeWhileDragging: false,
createMarker: function () {
return null;
}
});
return r;
}
var control = getRoute();
var routeLayer = L.layerGroup([control]); <---- HERE I GET THE ERROR
Any ideas?
In Leaflet, Controls are different from Layers.
In particular, you cannot make them children of a Layer Group.
As shown in Leaflet Routing Machine plugin home page, you just need to use the addTo() method to add your Control to the map:
L.Routing.control({
waypoints: waypoints
}).addTo(map);
In your precise case:
getRoute().addTo(map);
RxJS beginner here: I have problems with saving and tracking data changes using RxJS. Say I structure my app in small views/widgets and every view/widget has its own state and should do things on data changes. How do I do that?
More concrete example. Let's say I have a widget called Widget and Widget has a title and button. The state should contain the title and the information if the button was already clicked. From reading the docs of RxJS it seems this would be a good starting point:
var widgetState = new Rx.Subject().startWith({
wasClicked: false,
title: 'foo'
});
Now I want to be notified if some data changes:
var widgetStateChanges = widgetState.subscribe(function(data) {
console.log('data: ', data);
// what do i do with the data here?
// i would like to merge the new data into the old state
});
widgetStateChanges.onNext({ title: 'bar' });
I listen to the changes, but I don't know how to save them. I would also like to do special things, if a certain data change happens. Something like this.
widgetStateChanges.filter(function(e) {
return e.wasClicked;
}).do(function(e) {
console.log('Do something because was clicked now.');
});
However I can't filter a subscription (widgetStateChanges), only a subject (widgetState).
Use a BehaviorSubject to track observable state:
var widgetState = new Rx.BehaviorSubject({ wasClicked: false, title: 'foo' });
// change state, probably in response to UI events
// Note we always set the full state, not just the "delta"
widgetState.onNext({ wasClicked: true, title: 'foo2' });
// example listening to title input field and updating state
// assumes rxjs-jquery
$("#title").onAsObservable("change").subscribe (function (ev) {
var oldState = widgetState.value;
var newTitle = $("#title").val();
// do not mutate the oldState object, instead clone it and change the title
var newState = $.extend({}, oldState, { title: newTitle });
// send the update
widgetState.onNext(newState);
});
// listen to new state values, probably to update your HTML?
widgetState.subscribe(function (newState) { ... });
// listen only when wasClicked is true
widgetState
.filter(function (s) { return s.wasClicked; })
.subscribe(function (s) { ... });