Working with this example from Bing Maps V8
This line kicks off the map search: geocodeQuery("New York, NY");
This code fragment contains the callback function (which places the pin on the map):
var searchRequest = {
where: query,
callback: function (r) {
//Add the first result to the map and zoom into it.
if (r && r.results && r.results.length > 0) {
var pin = new Microsoft.Maps.Pushpin(r.results[0].location);
map.entities.push(pin);
map.setView({ bounds: r.results[0].bestView });
}
},
errorCallback: function (e) {
//If there is an error, alert the user about it.
alert("No results found.");
}
};
If I want to execute multiple searches, I can write:
geocodeQuery("New York, NY");
geocodeQuery("Queens, NY");
Now I have 2 pins on the map.
Next step: if I want to label the pins I'd extend the 'new pushpin code':
var pin = new Microsoft.Maps.Pushpin(r.results[0].location,
{
title: 'Queens',
text: '2'
});
Question:
The pins are placed by the callback function. Since I want each pin to have different text, how do I tweak this sample code so that I can pass new parameters to the callback function?
You would need to add an argument to the geocodeQuery function (maybe called title) and use that when you create the pin in the callback. Then you could simply call geocodeQuery("Queens, NY", "Queens")
function geocodeQuery(query, title) {
//If search manager is not defined, load the search module.
if (!searchManager) {
//Create an instance of the search manager and call the geocodeQuery function again.
Microsoft.Maps.loadModule('Microsoft.Maps.Search', function () {
searchManager = new Microsoft.Maps.Search.SearchManager(map);
geocodeQuery(query, title);
});
} else {
var searchRequest = {
where: query,
callback: function (r) {
//Add the first result to the map and zoom into it.
if (r && r.results && r.results.length > 0) {
var pin = new Microsoft.Maps.Pushpin(r.results[0].location, {
title: title,
text: '2'
});
map.entities.push(pin);
map.setView({ bounds: r.results[0].bestView });
}
},
errorCallback: function (e) {
//If there is an error, alert the user about it.
alert("No results found.");
}
};
//Make the geocode request.
searchManager.geocode(searchRequest);
}
}
Related
I'm trying to get a map that when you click at it generates a filter of a shapefile and then adds a layer only with the feature that matches the place where you click it.
I find this example http://plnkr.co/edit/o5Q0p3?p=preview&preview but in my case I need that the layer isn't added to map at fist. So, with only the map of leaflet you click and generates the filter of the shapefile and then you can see the feature.
This is what I have:
'''
var shpfileM = new L.Shapefile('assets/Muncipios.zip', {
onEachFeature: function (feature, layer) {
if (feature.properties) {
layer.bindPopup(Object.keys(feature.properties).map(function (k) {
return k + ": " + feature.properties[k];
}).join("<br />"), {
maxHeight: 200
});
}
},
style: {
color: 'green',
fillColor: 'green',
fillOpacity: 0.1
}
});
function onMapClick(e) {
var longlat = map.getLatLng();
eachMun = L.Shapefile(shpfileM, {
filter: filter(feature),
if (feature = longlat) { return
L.layer(feature).addTo(map)
}
})};
map.on('click', onMapClick);
Thank you a lot! and I hope that many other people that might have the same question finds the answers useful. (:
it is little more convenient to use shapefile-js https://github.com/calvinmetcalf/shapefile-js instead of L.Shapefile. Because it allows to load geojson data once and reuse it on each click.
also include turf.js
<script src="https://cdn.jsdelivr.net/npm/#turf/turf#5/turf.min.js"></script>
// create map
var mymap = L.map(...)
// create variable, where json from shp, will be once loaded and stored
let jsonData = null
// create variable for polygons group
let group = null
// read shp data and put it to variable
shp("filepath.zip").then(function (geojson) {
console.log(geojson)
jsonData = geojson
});
function onMapClick(e) {
// get point from click event and create turf point
let point = turf.point([e.latlng.lng, e.latlng.lat])
// clear polygons, if they already exist
if (group) {
group.clearLayers()
group.removeFrom(mymap)
}
group = L.geoJSON(jsonData, {
style: {...},
filter: (el) => {
// try catch, to avoid invalid geometry errors
try {
// check point in polygon
return turf.booleanPointInPolygon(point, el.geometry)
} catch(error) {
return false
}
},
onEachFeature: function(){...}
})
// add filtered layer group to map
group.addTo(mymap)
}
mymap.on('click', onMapClick);
OR using L.Shapefile
function onMapClick(e) {
let point = turf.point([e.latlng.lng, e.latlng.lat])
if (group) {
group.clearLayers()
group.removeFrom(mymap)
}
group = new L.Shapefile('filepath.zip', {
// same options as in previous example
})
group.addTo(mymap)
};
mymap.on('click', onMapClick);
I am calling a function in Javascript and passing few parameters(title, subTitle, text). I debugged and checked their values are being passed to it, I want to use them in a callback method but their values are not defined in that scope. How to solve this?
function geocodeQuery(query, title, subTitle, text) {
//If search manager is not defined, load the search module.
if (!searchManager) {
//Create an instance of the search manager and call the geocodeQuery function again.
Microsoft.Maps.loadModule('Microsoft.Maps.Search', function () {
searchManager = new Microsoft.Maps.Search.SearchManager(map);
geocodeQuery(query);
});
} else {
var searchRequest = {
where: query,
callback: function (r) {
//Add the first result to the map and zoom into it.
if (r && r.results && r.results.length > 0) {
var pin = new Microsoft.Maps.Pushpin(r.results[0].location,
{
title: title,
subTitle: subTitle,
text: text
});
map.entities.push(pin);
map.setView({ bounds: r.results[0].bestView });
}
},
errorCallback: function (e) {
//If there is an error, alert the user about it.
alert("No results found.");
}
};
//Make the geocode request.
searchManager.geocode(searchRequest);
}
}
In my app I am trying to get a Region (city) to store in my Location model along with lngLat and address.
The thing is that for new locations it would be easy as when I would create I would do that. For old locations I wrote this bit of code
function geocodeLatLng(geocoder, latlngStr, callback) {
var latlng = { lat: parseFloat(latlngStr.split(',')[0]), lng: parseFloat(latlngStr.split(',')[1]) };
var city;
geocoder.geocode({ 'location': latlng }, function (results, status) {
if (status === google.maps.GeocoderStatus.OK) {
var address = results[0].address_components;
for (var p = address.length - 1; p >= 0; p--) {
if (address[p].types.indexOf("locality") != -1) {
console.log(address[p].long_name);
city = address[p].long_name;
callback(city);
}
}
}
});
}
And I am calling it like this
self.getRegion = function () {
for (var i = 0; i < self.rawLocations().length; i++) {
var location = self.rawLocations()[i];
setTimeout(
geocodeLatLng(geocoder, location.systemRepresentation(), function (res) {
}), 250);// End of setTimeOut Function - 250 being a quarter of a second.
}
}
The issue is that I get over_query_limit after 5 calls. I will store the Location it self in the database for now I would have to do this to fix the old locations.
Any headers?
Google maps javascript library has a maximum calls per second as well as an hourly rate, are you trying to geocode at a rate faster than their per second rate possibly?
5 does seem low as their own documents inform users that it is 50 per second (https://developers.google.com/maps/documentation/geocoding/usage-limits)
Also have you signed up for a key and are using it? This could make a difference (if the google account is old as signed up for maps API sometime you can use the system without a key)
Objective
I wish to remove the duplication code brought by the anonymous function calls.
Background
I am doing a very simple project where I use Google Maps API to show a map with two searchboxes. The user puts a start address and an end address in those boxes and I show markers in the map.
To achieve this I have two anonymous functions for the listeners, which are exactly equal except for one point - one uses the startSearchBox and the other one the endSearchBox.
What I tried
This duplication of code is unnecessary, and so I tried to pass the searchboxes as a parameter to the anonymous function, however that didn't work.
I also considered creating the searchboxes as global variables, but that is a bad practice I wish to avoid.
How can I eliminate the duplication in this code?
Code
function initSearchBoxes() {
// Create the search box and link it to the UI element.
let startInput = document.getElementById('start-input');
let startSearchBox = new google.maps.places.SearchBox(startInput);
let endInput = document.getElementById('end-input');
let endSearchBox = new google.maps.places.SearchBox(endInput);
// Bias the SearchBox results towards current map's viewport.
map.addListener('bounds_changed', function() {
startSearchBox.setBounds(map.getBounds());
endSearchBox.setBounds(map.getBounds());
});
startSearchBox.addListener('places_changed', function() {
deleteAllMarkers();
let places = startSearchBox.getPlaces();
if (places.length == 0) {
return;
}
// For each place, get the icon, name and location.
let bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
// // Create a marker for each place.
let newMarker = createMarker(place.geometry.location, place.name, markerLabels.nextSymbol(), true);
markerLib.trackMarker(newMarker);
newMarker.setMap(map);
if (place.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(place.geometry.viewport);
}
else {
bounds.extend(place.geometry.location);
}
});
map.fitBounds(bounds);
});
endSearchBox.addListener('places_changed', function() {
deleteAllMarkers();
let places = endSearchBox.getPlaces();
if (places.length == 0) {
return;
}
// For each place, get the icon, name and location.
let bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
// // Create a marker for each place.
let newMarker = createMarker(place.geometry.location, place.name, markerLabels.nextSymbol(), true);
markerLib.trackMarker(newMarker);
newMarker.setMap(map);
if (place.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(place.geometry.viewport);
}
else {
bounds.extend(place.geometry.location);
}
});
map.fitBounds(bounds);
});
}
You can wrap your callback function in another "factory" function. The factory will take a parameter (the search box reference) and then it will return the actual handler:
function makeSearchHandler(searchBox) {
return function() {
deleteAllMarkers();
let places = searchBox.getPlaces();
if (places.length == 0) {
return;
}
// For each place, get the icon, name and location.
let bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
// // Create a marker for each place.
let newMarker = createMarker(place.geometry.location, place.name, markerLabels.nextSymbol(), true);
markerLib.trackMarker(newMarker);
newMarker.setMap(map);
if (place.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(place.geometry.viewport);
}
else {
bounds.extend(place.geometry.location);
}
});
map.fitBounds(bounds);
};
}
That function contains the code from your original, but instead of directly referring to either startSearchBox or endSearchBox, it uses the parameter passed to the factory. The returned function will therefore work like yours, but the code is only present once.
You can then use that function to create the callbacks:
startSearchBox.addListener('places_changed', makeSearchHandler(startSearchBox));
endSearchBox.addListener('places_changed', makeSearchHandler(endSearchBox));
I am trying to extend the google maps marker example from the this website by adding the extraction of the country name.
Since google maps returns an array with details to each address, I need to iterate over the array to find the entry e.g. for "country". Since I would like to reuse the piece of code, I create a function.
But I am not sure how to call this function (here call find(components, item)) correctly.
I do not get any return from the find function. How do I call the find function correctly from within the select function?
*Could there be a different mistake why I get an empty return?*
Thank you for your thoughts and suggestions!
$(document).ready(function() {
initialize();
$(function() {
$("#address").autocomplete({
//This bit uses the geocoder to fetch address values
source: function(request, response) {
geocoder.geocode( {'address': request.term }, function(results, status) {
response($.map(results, function(item) {
return {
label: item.formatted_address,
value: item.formatted_address,
components: item.address_components,
latitude: item.geometry.location.lat(),
longitude: item.geometry.location.lng()
}
}));
})
},
// address_component selection
find: function(components, item) {
for(var j=0;j < components.length; j++){
for(var k=0; k < components[j].types.length; k++){
if(components[j].types[k] == "country"){
return components[j].long_name;
}
}
}
},
//This bit is executed upon selection of an address
select: function(event, ui) {
$("#latitude").val(ui.item.latitude);
$("#longitude").val(ui.item.longitude);
$("#country").val(this.find(ui.item.components, "country"));
var location = new google.maps.LatLng(ui.item.latitude, ui.item.longitude);
marker.setPosition(location);
map.setCenter(location);
}
});
});
The find() function is defined in the autocomplete options object.
If you want to access your find function you'll have to define it outside the autocomplete options or to get a pointer to it :
var find = $(event.target).data("autocomplete").options.find;
This is because jQuery changes the context of functions with the element that is attached to the listener.