Google Maps API v3 marker setPosition not working in Safari (sencha touch) - iphone

I am using Sencha Touch to develop a mobile version of a Bus Tracker for Boston University. The problem I a running into is that the method setPosition() for a google.maps.Marker is not rendering the position change in Safari or any Mobile browser.
The code set up is as follows:
I initialize an empty markers array
I initialize the map using Ext.Map() (sencha call)
I load data using a JSONP request every 5 seconds interval
Every time I get new data, I check if I have a marker for that bus id inside my markers array
If I don't I create a new marker and push it into my markers array
Otherwise I call setPosition with my new position on that marker in my markers array.
I then run a check to make sure the marker's position got updated to the position received from my JSON request
I have verified (I think) that the marker inside the markers array is getting the new position everytime. Also, in Chrome and Firefox, my buses move (as expected), but in safari and iPhone/Android browsers, nothing moves.
Here is the code snippet:
var markers = {};
var busesFunc = function()
{
Ext.util.JSONP.request({
url: 'http://m.cms-devl.bu.edu/rpc/bus/livebus.json.php',
callbackKey: 'callback',
params: {
},
callback: function(data) {
buses = data.ResultSet.Result;
for (var i = 0, ln = buses.length; i < ln; i++) {
var bus = buses[i];
var position = new google.maps.LatLng(bus.lat, bus.lng);
if(!markers[bus.id])
{
markers[bus.id] = new google.maps.Marker({
map: map.map,
title: 'hello',
clickable: true,
draggable: false,
position: position,
icon: "images/bg.png",
zIndex: 100
});
}
markers[bus.id].setPosition(position);
//markers[bus.id].setIcon("images/bg.png");
//markers[bus.id].setMap(map.map);
//markers[bus.id].setMap(map.map);
if(bus.lat != markers[bus.id].position.lat() || bus.lng != markers[bus.id].position.lng())
{
console.log(bus.id + " " + bus.lat + " " + bus.lng);
console.log(bus.id + " " + markers[bus.id].position.lat() + " " + markers[bus.id].position.lng());
}
}
}
});
}
setInterval(busesFunc, 5000);
You can view the sample here: http://www.bu.edu/nisdev/students/luiscarr/liveBusMobile/
And the whole javascript is called functions.js (I can't post more than one link)

[Sencha Person] markers not showing up is a known bug in the 0.93 beta release. The 0.94 release (current one) has this fixed.

Problem solved by making a unique request every interval. I figured it was a caching problem after some more debugging. So I added a timestamp parameter to the JSONP request and it was all fixed.

Related

Esri-Leaflet - Search within a distance

I need to design an application using a feature layer stored in ArcGIS online. Using a geocoder/search, I need to be able to enter an address and select a distance (1 block, 2 blocks, etc). The result will show the new point, a distance radius, and all points within the radius. I would also like to have a table of the results.
What I need is exactly like this app created by Derek Eder from DataMade: https://carto-template.netlify.app/, except mine needs the data stored in a secured ArcGIS layer. Can anyone point me to an example, tutorial, etc with an esri-leaflet implementation similar to this application? I have spent the past five days trying to convert the code, and I feel like I am getting no where.
Here is a link to guthub: https://github.com/datamade/searchable-map-template-carto
-------UPDATE-------
Seth - I can get the layer to display; however, the query to join the searched point with the layer does not work. I imagine I’m leaving something out, because the console error reads “token required”. See below:
const radius = 1610;
/**************************************************************************************************/
// ArcGIS Authoization
/**************************************************************************************************/
$("#loginModal").modal({ backdrop: 'static', keyboard: false });
// submit element of form
var submitBtn = document.getElementById('btnArcGISOnline');
// add event listener to form
submitBtn.addEventListener('click', addServicesFromServer);
// create map and set zoom level and center coordinates
var map = L.map('mapCanvas', {
}).setView([30.46258, -91.13171], 12);
// set basemap to Esri Streets
L.esri.basemapLayer('Streets').addTo(map);
var layerurl = 'secure/layer/URL';
var tokenUrl = 'https://www.arcgis.com/sharing/generateToken';
// function to make request to server
function serverAuth(server, username, password, callback) {
L.esri.post(server, {
username: username,
password: password,
f: 'json',
expiration: 86400,
client: 'referer',
referer: window.location.origin
}, callback);
}
// function to run when form submitted
function addServicesFromServer(e) {
// prevent page from refreshing
e.preventDefault();
// get values from form
var username = document.getElementById('username').value;
var password = document.getElementById('password').value;
// generate token from server and add service from callback function
serverAuth(tokenUrl, username, password, function (error, response) {
if (error) {
return;
}
// add layer to map
var featureLayer = L.esri.featureLayer({
url: layerurl,
opacity: 1,
token: response.token
});
featureLayer.addTo(map);
$("#loginModal").modal("hide");
}); // end serverAuth call
} // end addServicesFromServer call
// HARNESS GEOCODER RESULTS
let circle;
// GeoSearch
const search = L.esri.Geocoding.geosearch({
useMapBounds: false,
expanded: true,
collapseAfterResult: false
});
search.addTo(map);
search.on("results", (results) => {
if (results && results.latlng) {
if (circle) {
circle.remove();
}
circle = L.circle(results.latlng, { radius });
circle.addTo(map);
queryLayer(results.latlng);
}
});
// SET UP QUERY FUNCTION
function queryLayer(point) {
const query = L.esri.query({ url: layerurl }).nearby(point, radius);
query.run(function (error, featureCollection, response) {
if (error) {
console.log(error);
return;
}
console.log(featureCollection.features);
populateList(featureCollection.features);
});
}
// WRITE RESULTS INTO A LIST
function populateList(features) {
const list = document.getElementById("results-list");
let listItems = "";
features.forEach((feature) => {
listItems =
listItems +
`
<li>
Place: ${feature.properties?.Location} <br>
Lat: ${feature.properties?.Latitude} <br>
Lng: ${feature.properties?.Longitude} <br>
</li>
`;
list.innerHTML = listItems;
});
}
I attempted to pass the token to the query as pasted below, but then I get an invalid token error.
var layerUrl_token = layerurl + "?token=" + response.token;
I also tried using turf.js, but I haven’t been successful. I know turf.js uses long/lat, but I haven’t even been able to get the correct syntax to pull the lat and long from the feature layer.
What you're trying to do is not too hard. While there are a handful of tutorials on different parts of what you want to do, let's piece things together. I'm going to use esri-leaflet-geocoder for my search functionality, as its consistent with esri-leaflet, and IMO its one of the best geocoders available for leaflet.
Setting up the geocoder
After setting up a basic leaflet map, let's import esri-leaflet and esri-leaflet-geocoder, and create a geocoder:
import L from "leaflet";
import * as EL from "esri-leaflet";
import * as ELG from "esri-leaflet-geocoder";
const search = ELG.geosearch({
useMapBounds: false,
expanded: true,
collapseAfterResult: false
});
search.addTo(map);
Don't forget to add the geocoder css to your html, as shown in the documentation example.
Add your layer:
const layerurl = "YOUR_LAYER_URL";
const featureLayer = EL.featureLayer({ url: layerurl });
featureLayer.addTo(map);
If you are using an authenication-required layer, you will need to get a token and use it as one of the options in featurelayer, (featureLayer({ url: layerurl, token: token })). If you're not sure how to get a token, make a comment and I can add some code for that, but there are some nice tutorials already available for that.
Harness the results of the search
The ELG.geosearch comes with a results event that you can harness. It is called when the user selects one of the results in the autocomplete dropdown of the geosearch. In that event, we can get the location data of location the user selected. We center the map there (which is a default of the geosearch actually), draw a circle with a given radius, and perform a query (more on that layer):
let circle;
search.on("results", (results) => {
if (results && results.latlng) {
if (circle) {
circle.remove();
}
circle = L.circle(results.latlng, { radius });
circle.addTo(map);
queryLayer(results.latlng);
}
});
Query the layer
Now we know the latlng of the location the user selected from the search. We can create an esri-leaflet query, which can query your feature layer in various ways. We'll see up a nearby query, which will query the layer for any features within a given radius of a point:
function queryLayer(point) {
const query = EL.query({ url: layerurl }).nearby(point, radius);
query.run(function (error, featureCollection, response) {
if (error) {
console.log(error);
return;
}
populateList(featureCollection.features);
});
}
If you are querying an authenticated layer, you'll need to add a token to the request. I'm fairly certain the way to do this is like so:
function queryLayer(point) {
const query = EL.query({ url: layerurl })
.token(<your_token_here>)
.nearby(point, radius);
// ... same as above
}
You may also be able to run a query directly off of your layer:
featureLayer.query().nearby(point, radius)
I'm not as familiar with this second way, but you can read more about it here: Query a feature layer.
Render to the page
Once we .run the query, we will have access to the results in the form of a featureCollection. We can loop through the features of that featureCollection and render some HTML:
function populateList(features) {
const list = document.getElementById("results-list");
let listItems = "";
features.forEach((feature) => {
listItems =
listItems +
`
<li>
Place: ${feature.properties?.Location} <br>
Lat: ${feature.properties?.Latitude} <br>
Lng: ${feature.properties?.Longitude} <br>
</li>
`;
list.innerHTML = listItems;
});
}
In this particular example, I am using a point layer I made that is being served through arcgis online. This point layer does not have address data, so feature.properties doesn't contain any address info. For your featurelayer, the attributes of your layer will be available in a feature.properties. So depending on what's there, you might want to use feature.properties?.address or whatever. This last bit is just an example, you will probably customize that a lot differently for your own purposes.
Working Codesandbox
Try searching heavily populated areas in this example. Note that in this featurelayer there are many overlapping locations, so there are more results in the list than it looks like there are markers on the map.
Also note, this example I'm posting using esri-leaflet and esri-leaflet-geocoder versions 2^. These were just updated to versions 3 about 1-2 months ago, and the new versions require use of an API key in the geocoder and in the layer declaration, so if you want to use the latest versions (recommended), you will need to add those in. I used version 2 so as not to expose an API key in a sandbox (and I sort of hate the new API key requirement . The new arcgis developers documentation for esri-leaflet has some examples of that, but the official documentation has not yet been updated to match those examples.

Leaflet map completely grey programmatically opening a popup tofa marker

I declare a leaflet map with
<div id="map" class="map-div"></div>
end initialize it with
var map = L.map('map').setView([51.178882, -1.826215],16);
$scope.map = map;
// OSM Mapnik
var osmUrl = "<a href='http://www.openstreetmap.org'>Open StreetMap</a>";
L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© ' + osmUrl,
maxZoom: 18,
}).addTo(map);
I grab some data from my server, and and markers to the map, in a loop, by calling this function (it's AngularJS, but I doubt that that plays a role):
$scope.AddMarkerToMap = function(companyData, index, array)
{
var companyName = companyData.company_name;
var latitude = companyData.latitude;
var longitude = companyData.longitude;
var cssClassname = 'comapny_has_no_present_workers';
if (companyData['currentWorkers'] > 0)
cssClassname = 'comapny_has_present_workers';
var pubLatLng = L.latLng(latitude,longitude);
// see https://leafletjs.com/reference-1.4.0.html#marker
var marker = L.marker(pubLatLng,
{
// this is the tooltip hover stuff
title: companyData['currentWorkers'] + ' current matches ' + companyData['previousWorkers'] + ' previous matches',
// see https://leafletjs.com/reference-1.4.0.html#icon
// this is a permanent label.
icon: new L.DivIcon({
className: cssClassname,
////html: '<img class="my-div-image" src="http://png-3.vector.me/files/images/4/0/402272/aiga_air_transportation_bg_thumb"/>'+
//// '<span class="my-div-span">RAF Banff Airfield</span>'
html: '<span>' + companyName + '</span>'
})
}).addTo($scope.map);
// see https://leafletjs.com/reference-1.4.0.html#popup
marker.bindPopup("<b>Hello world!</b><br>I am a popup.").openPopup();
}; // AddMarkerToMap()
And the entire map is suddenly grey - with no problems reported in the developer console.
If I comment out the line
marker.bindPopup("<b>Hello world!</b><br>I am a popup.").openPopup();
then everything displays as expected.
The code seems correct, as per the Leaflet documentation.
[Updtae] I just checked and if I only marker.bindPopup("<b>Hello world!</b><br>I am a popup."), the the map displays and I can click on the marker to display the popup. But when I try to programmatically open it with .openPopup(); the map is all grey.
[Update++] the map and its markers display just fine, with any one of
marker.bindPopup("<b>Hello world!</b><br>I am a popup.");
$scope.map.fitBounds(bounds, {padding: [50, 50]});
but with both, the map is grey :-(
What am I doing wrongly?
I think the issue comes from trying to change the map view (possibly through openPopup with autoPan, which is on by default) too often, typically in a loop without giving any delay for the map to actually set the view between each call.
IIRC, this is already identified as a limitation in Leaflet, but I could not find the exact thread in the issue tracker unfortunately.
Normally, a very simple fix is simply to remove the map view changes within your loop, and keep only the very last one.
In your case, if you have the default behaviour of only 1 Popup being opened at a time, then that would definitely be a valid solution: just open the popup of your last Marker.
If you did configure your map to keep several Popups open simultaneously, and you do want to open all of them through your loop, then make sure to disable autoPan (at least during your loop).

MapboxGL: querying rendered features after multiple geocodes

Situation: I have a working site where upon entering an address, MapboxGL marks a point on the map and queries a polygon layer (queryRenderedFeatures) and displays the polygon feature containing the point.
This works; however, if I then want to geocode a second address that changes the map view, it fails the second time because map.queryRenderedFeatures returns an empty array.
var userDistrictsGeoJson;
map.on('load', function() {
//add layers from Mapbox account
addLayers(); //details left out of example, but this works.
// Listen for geocoding result
// This works the first time through, but fails if the user searchs for a second address because queryRenderedFeatures is working with a smaller set of features
geocoder.on('result', function(e) {
//need to clear geojson layer and
userDistrictsGeoJson = {
"type": "FeatureCollection",
"features": []
};
map.getSource('single-point').setData(e.result.geometry);
//project to use (pixel xy coordinates instead of lat/lon for WebGL)
var point = map.project([e.result.center[0], e.result.center[1]]);
var features = map.queryRenderedFeatures(point, { layers: ['congress-old'] });
var filter = featuresOld.reduce(function(memo, feature){
// console.log(feature.properties);
memo.push(feature.properties.GEOID);
return memo;
}, ['in', 'GEOID']);
map.setFilter('user-congress-old', filter);
var userCongressOldGeoJson = map.querySourceFeatures('congressional-districts', {
sourceLayer: 'congress_old',
filter: map.getFilter('user-congress-old')
});
userDistrictsGeoJson.features.push(userCongressOldGeoJson[0]);
var bbox = turf.bbox(userDistrictsGeoJson);
var bounds = [[bbox[0], bbox[1]], [bbox[2], bbox[3]]];
map.fitBounds(bounds, {
padding: 40
});
}); //geocoder result
}); //map load
So like I said, everything that runs on the geocodes 'result' event works the first time through, but it seems that on the second time through (user searches new address, but doesn't reload map) queryRenderedFeatures returns a smaller subset of features that doesn't include the tiles where the geocoder lands.
Any suggestions are much appreciated.
I ended up solving this by triggering the querying code once on 'moveend' event.
So now the syntax is:
geocoder.on('result', function(e){
map.once('moveend', function(){
.... rest of code
}
}
I thought I had tried this before posting the question, but seems to be working for me now.

How do I set a version 7.0 bing map center to a location

I am using version 7.0 of the Bing Maps API. After creating the map, an array of pins are pushed into the EntityCollection object of the map class. Next, I want to center the map so that all of these pins are viewed on the map. The map's zoom is large enough to accommodate this. In the previous version, map.setMapView() was used, but BING Maps 7.0 has erased this function.
Some code for relevance:
map = new Microsoft.Maps.Map(document.getElementById("myMap"), mapOptions);
map.getCredentials(function(credentials) {
var searchRequest = 'https://dev.virtualearth.net/REST/v1/Locations/' + address + '?output=json&jsonp=getLatLong&key=' + credentials;
var mapscript = document.createElement('script');
mapscript.type = 'text/javascript';
mapscript.src = searchRequest;
document.getElementById('myMap').appendChild(mapscript);
});
function getLatLong(json){
findPlaceResults = new Microsoft.Maps.Location(json.resourceSets[0].resources[0].point.coordinates[0], json.resourceSets[0].resources[0].point.coordinates[1]);
myShape = new Microsoft.Maps.Pushpin(findPlaceResults);
//...
var pins = new Array();
for (var i = 0; i < AllLocations.length; i++) {
var shape = new Microsoft.Maps.Location(AllLocations[i].Latitude, AllLocations[i].Longitude);
var pins = new Microsoft.Maps.Pushpin(shape);
map.entities.push(pins);
}
map.entities.push(myShape);
if (map.entities.getLength() > 0) {
//map.SetMapView(pins);
}
Code TLDR: Stuff happens, try to SetMapView, doesn't work.
Any thoughts would be helpful!
When you creating your pins you need to create helper array will that lead to your goal.
Create array that contains all the locations converted to Microsoft location objects.
// The array
var arrLocations= [];
for (var i = 0; i < AllLocations.length; i++) {
var shape = new Microsoft.Maps.Location(AllLocations[i].Latitude,AllLocations[i].Longitude);
// You add those two lines.
var yourLocation= new Microsoft.Maps.Location(AllLocations[i].Latitude,AllLocations[i].Longitude);
arrLocations.push(yourLocation);
var pins = new Microsoft.Maps.Pushpin(shape);
map.entities.push(pins);
}
Now you use bing maps feature that gives you best zoom and pointing according to given locations.(The LocationRect)
var bestView = Microsoft.Maps.LocationRect.fromLocations(arrLocations);
Then you set the map view according to the best view that we found.
setTimeout((function () {
map.setView({ bounds: bestView });
}).bind(this), 1000);
Well, I found the answer at this website http://www.i-programmer.info/projects/131-mapping-a-gis/1609-getting-started-with-bing-maps-ajax-control-70.html?start=1
"If you are familiar with earlier versions of the Map object you need to know that the new version has far fewer methods. The idea is that instead of having lots of methods the new control has a few methods that accept complex objects as a parameter that specifies lots of settings.
For example, the original map control's SetCenter method will move the map location to the specified latitude and longitude. The new V7 map control has a setView method which accepts a ViewOptions object which in turn has a center property that can be set to a Location object which specifies the center of the map."

VEMap Pan triggers VEMap.onclick

I'm using the Virtual Earth (or Bing!...) SDK and need to attach an event when someone clicks the map. Unfortunately panning the map also triggers the onclick event. Does anyone know of a work around?
function GetMap(){
map = new VEMap('dvMap');
map.LoadMap(new VELatLong(35.576916524038616,-80.9410858154297), 11, 'h',false);
mapIsInit = true;
map.AttachEvent('onclick', MapClick);
}
function MapClick(e){
var clickPnt = map.PixelToLatLong(new VEPixel(e.mapX,e.mapY));
Message('Map X: ' + clickPnt.Longitude + '\nMap Y: ' + clickPnt.Latitude + '\nZoom: ' + e.zoomLevel);
}
I'm encountering the same problem. It is certainly not what I would expect, a click event in my terminology implies that the distance between mouseDown and mouseUp is below a certain threshold.
Here is some code that works in my experiments:
<script type="text/javascript">
var mouseDownLocation;
var mouseClickThreshold = 5;
function init()
{
var map = new VEMap('myMap');
map.LoadMap(new VELatLong(-27.15,153),8,'r' ,false);
map.AttachEvent("onmousedown", function(e) {
var x = e.mapX;
var y = e.mapY;
mouseDownLocation = new VEPixel(x, y);
});
map.AttachEvent("onmouseup", function(e) {
var x = e.mapX;
var y = e.mapY;
if(Math.abs(mouseDownLocation.x - x) +
Math.abs(mouseDownLocation.y - y) > mouseClickThreshold) {
return;
}
pixel = new VEPixel(x, y);
var LL = map.PixelToLatLong(pixel);
document.getElementById("myMapInfo").innerHTML =
"Pixel X: " + x + " | Pixel Y: " + y +
"<br /> LatLong: " + LL +
"<br/>" + e.eventName;
});
}
</script>
The message will be displayed only if the mouse didn't move too much between the down and up events, i.e. a normal click should trigger it, a drag shouldn't.
This is using the VE API in version 6.2 and expects two divs with IDs "myMap" and "myMapInfo". It's also code that's experimental and could be improved upon, but the general approach seems ok.
It all depends on what you're trying to do. You could check for which mouse button was pressed during the onclick event handler. For example: If it was the Left Mouse Button then do nothing, else if it's the Right Mouse Button then do your logic to display a message, or plot pushpin, etc.
To clarify, only Panning the Map using the Mouse will trigger the onclick event. If you use the little arrows on the navigation dashboard, it will not trigger the onclick event.
Thanks Peter, that is working great with 6.3 as well. I'm discovering Bing maps and I'm a bit lost in the event handlers, so that helped!