I'm trying to produce a mapping application with Bing Maps with a button that will retrieve a JSON string and places pins on the map based on the center of the map.
That is working fine, but I'm running into two issues that I'm having trouble diagnosing.
The first is that when I move the map after placing the pins, the majority of them disappear from the map except for 1-3. I've figured out that the pins are still being held in map.entities, but just aren't all displaying.
The second issue is that I have a click event on the pins, and sometimes when I click on a pin it will disappear (and sometimes reappear elsewhere on the map).
Here is my code:
function addPin() {
map.entities.clear();
var pinImg = "images/MapPin.jpg";
var latLong = {};
var name;
for (var i = 0; i < factualJson.response.data.length; ++i) {
latLong['latitude'] = factualJson.response.data[i].latitude;
latLong['longitude'] = factualJson.response.data[i].longitude;
name = factualJson.response.data[i].name;
var pin = new Microsoft.Maps.Pushpin(latLong, {
icon: pinImg,
anchor: new Microsoft.Maps.Point(latLong['latitude'], latLong['longitude']),
draggable: true,
width: 48,
height: 48
});
Microsoft.Maps.Events.addHandler(pin, 'click', displayName);
pin.title = name;
pin.id = 'pin' + i;
map.entities.push(pin);
}
document.getElementById("arrayLength").innerHTML = "Number of locations: " + map.entities.getLength();
}
function displayName(e) {
document.getElementById("name").innerHTML = "";
if (this.target.id != -1) {
document.getElementById("name").innerHTML = this.target.title;
}
}
function boot() {
Microsoft.Maps.loadModule('Microsoft.Maps.Overlays.Style', { callback: getMap });
}
function getMap() {
map = new Microsoft.Maps.Map($gel("bingMap"), {
credentials: getKey(),
customizeOverlays: true,
enableClickableLogo: true,
enableSearchLogo: true,
showDashboard: true,
showBreadcrumb: true,
showCopyright: true,
zoom: 10,
labelOverlay: Microsoft.Maps.LabelOverlay.hidden
});
setGeoLocation();
//setTimeout(optimizeMap, 100);
window.onresize = resizeWin;
resizeWin();
}
Currently I make an ajax call from the button, and the callback function calls 'AddPin' which adds the pins to the map. I thought I'd add in the map initialization code in case it was relevant. Currently boot() is called on body load.
For me the solution was similar to yours #canadian coder
Microsoft.Maps.Location() only accepts float values, no strings and Int.
I use MVC architecture and passed a string using a model. Later i converted that string to float and passed to Location.
Problem solved.
var pushpin = new Microsoft.Maps.Pushpin(
center, { icon: '/Content/BingPushpin.png', width: 50, height: 50, draggable: false });
pushpin.setLocation(new Microsoft.Maps.Location
(parseFloat(#Model.Latitude) , parseFloat(#Model.Longitude)));
dataLayer.push(pushpin);
locations.push(new Microsoft.Maps.Location
(parseFloat(#Model.Latitude) , parseFloat(#Model.Longitude)));
EDIT :
Later found out that problem still exist. Another reason can be that you are calling that Map to load twice. So check for any other instance of the map which is being loaded. In my case see below.
$(document).ready(function () {
loadSearchModule(); //calling Map to Load at a div without pushpins
//code to do something
getMapOnLocation();
}
function getMapOnLocation()
{//code to display map with pushpin
}
In the Above example I was telling the control to load my map with PushPins and when the page is fully Loaded load the map without pushpins.
Hope this helps :)
As always I need to ask the question before I figure it out myself.
The issue was that I needed to push a Microsoft location object into the pin and not an object. Like so:
var loc = new Microsoft.Maps.Location(47.592, -122.332);
And NOT my latLong object.
This also seemed to fix the issue of disappearing pins on click event.
Related
I have a map with bingmap and I implemented waypoints. I would like when I click in a waypoint it displays an info window.
if (!$scope.directionsManager) { _createDirectionsManager(); }
if($scope.directionsManager.getAllWaypoints().length < 2)
{
$scope.directionsManager.resetDirections();
$scope.waypoints.forEach(function (waypoint) {
var order_waypoint = new Microsoft.Maps.Directions.Waypoint({ location: new Microsoft.Maps.Location(waypoint.lat, waypoint.lng) });
console.log("order_waypoint", order_waypoint)
$scope.directionsManager.addWaypoint(order_waypoint);
})
}
var renderOption = {
itineraryContainer: document.getElementById('directionsItinerary'),
waypointPushpinOptions:{
// icon: "http://vignette3.wikia.nocookie.net/freeciv/images/1/1c/Crystal_128_penguin.png/revision/latest?cb=20071106133132&path-prefix=es",
// hoverIcon: "http://vignette3.wikia.nocookie.net/freeciv/images/1/1c/Crystal_128_penguin.png/revision/latest?cb=20071106133132&path-prefix=es",
// height: 10,
// width: 10,
draggable: false,
textOffset: new Microsoft.Maps.Point(-1, 3)
}
}
$scope.directionsManager.setRenderOptions(renderOption);
$scope.directionsManager.calculateDirections();
Thanks!
I haven't used the Bing maps API, but the docs on the Waypoint class make it look like you might need a custom pushpin in order to intercept its click event.
var location = new Microsoft.Maps.Location(waypoint.lat, waypoint.lng);
var pushpin = new Microsoft.Maps.Pushpin(location);
Microsoft.Maps.Events.addHandler(pushpin, 'click', function() {
// Implementation can go here
});
var order_waypoint = new Microsoft.Maps.Directions.Waypoint(
{location: location, pushpin: pushpin});
$scope.directionsManager.addWaypoint(order_waypoint);
It is possible you can get a reference to the default pushpin on order_waypoint without creating your own custom one, and use addHandler to bind the click event to it. I don't have running code to test that on, and I don't see a way to get a reference to the default pushpin, only the custom one. You could try it anyway (order_waypoint.getPushpin()) and see if it works.
I have some DOM elements that I don't directly have access to, as they are rendered from an API call. What I would like to do is add a class to this element once it's rendered on the DOM.
Using Template.rendered does not work, as the template renders properly before these DOM elements appear from the API.
My current solution is a Meteor.setTimeout—which might be the definition of a hack—and it only works about 90% of the time.
What is the best way to trigger a function when a particular DOM element is rendered?
Here is some of the relevant code from the API call:
Template.map.rendered = function() {
return this.autorun(function() {
var drawControl, drawnItems, mmap;
if (Mapbox.loaded()) {
L.mapbox.accessToken = '<API KEY>';
L.mapbox.config.FORCE_HTTPS = true;
mmap = L.mapbox.map('map', '<TOKEN>');
L.control.scale().addTo(mmap);
var featureGroup = L.featureGroup().addTo(mmap);
drawControl = new L.Control.Draw({
draw: {
polygon: false,
polyline: false,
rectangle: true,
circle: false,
marker: false
}
});
mmap.addControl(drawControl);
mmap.addControl(L.mapbox.geocoderControl('mapbox.places', {
autocomplete: true
}));
function showPolygonAreaEdited(e) {
e.layers.eachLayer(function(layer) {
showPolygonArea({ layer: layer });
});
}
function showPolygonArea(e) {
coords = {
lat1: normalizeLon(e.layer.toGeoJSON().geometry.coordinates[0][1][0]),
lon1: normalizeLat(e.layer.toGeoJSON().geometry.coordinates[0][1][1]),
lat2: normalizeLon(e.layer.toGeoJSON().geometry.coordinates[0][3][0]),
lon2: normalizeLat(e.layer.toGeoJSON().geometry.coordinates[0][3][1])
}
featureGroup.clearLayers();
featureGroup.addLayer(e.layer);
e.layer.openPopup();
}
mmap.on('draw:created', showPolygonArea);
mmap.on('draw:edited', showPolygonAreaEdited);
}
});
};
I've removed a lot of extraneous code, so this might not compile properly... But it has all the relevant bits.
The selector I initially tried to use was this:
Template.map.rendered = function() {
$('.leaflet-draw-section').attr('data-intro', 'Hello step one!')
...
...
But it didn't work, since the API elements hadn't rendered yet.
Looking at the map box api, it appears that L.mapbox.map does the rendering:
<script>
// Provide your access token
L.mapbox.accessToken = 'id1' //
// Create a map in the div #map
L.mapbox.map('map', 'id2');
// add your class to #map right here!!
</script>
If that doesnt work, them maybe L.mapbox.map is doing something asynchronous. They don't give you a callback, so a window.setTimout(func, 0) may be necessary
Make sure your code looks like this
Template.templatename.onRendered(function(){
//yourcode
});
The solution comes from the Mapbox API. There is a callback on "ready":
var layer = L.mapbox.tileLayer('mapbox.streets');
layer.on('ready', function() {
// the layer has been fully loaded now, and you can
// call .getTileJSON and investigate its properties
});
I'm new to maps and OpenLayers, but I'm investigating Openlayers because I'll need map functionality in my next project. The map is a WMS image of a medieval town, but without any geo-referencing information.
I found how to register events, but the problem is that the "eventargs" is not working as in the examples I found. In one of the examples they are getting the x and y values after the users panned like this:
map.events.register('moveend', map, function (e)
{
alert(e.xy);
});
If I try this in Visual Studio, e doesn't have an xy property. What am I missing? This is the code I have right now:
<script type="text/javascript">
var map, layer;
function init() {
var windowHeight = $(window).height();
var windowWidth = $(window).width();
var mapdiv = $('#map');
mapdiv.css({width: windowWidth + 'px', height: windowHeight + 'px'});
map = new OpenLayers.Map('map', { maxResolution: 1000 });
layer = new OpenLayers.Layer.Image(
'Globe ESA',
'[url]',
new OpenLayers.Bounds(-180.0, -12333.5, 21755.5, 90.0),
new OpenLayers.Size(windowWidth, windowHeight),
{numZoomLevels: 100}
);
map.addLayer(layer);
nav = new OpenLayers.Control.Navigation();
map.addControl(nav);
//events test
map.events.register('moveend', map, function (e)
{
alert(e.xy);
});
map.zoomToMaxExtent();
}
</script>
In the OpenLayers examples they don't use the eventargs, but I assume that there must be a way to get the zoomlevel, or the x and y after panning?
Thank you!
There is no explicit xy sent to the moveend event as a parameter to the listener. So, while you can write,
map.events.register('moveend', map, function(xy){
xy won't contain any useful information.
However, you can get information from the OpenLayers.Map itself, such as maxExtent and zoom, which you are passing in as the object to the moveend event.
map.events.register('moveend', map, function(){
console.log(map.maxExtent + " " + map.getZoom());
});
There are many other properties associated with the map, which you can see if you set a breakpoint inside the moveend callback. If you haven't looked at the source for map, it is well worth it to understand what is going on. OpenLayers.Map.js
You will see that the moveend event is triggered either without any parameters or with one, a boolean, zoomChanged.
I am working on a Tobacco shop finder Windows 8.1 Store application. The main idea is, that when the user opens the app a map appears zoomed in to his position. This map displays the tobacco shops as pushpins. The data of these shops are stored in a json file with the following structure:
{
addr: Some street 12,
lng: longitude,
lat: lattitude,
open: {10:00-23:00, 20:00-22:00, and 5 more}
}
It contains the address, the longitudinal position, the lattitudinal position and the open hours of the shop.
This is the javascript code I use to put the pushpins in the correct position on the map:
var pins = [];
$.getJSON(dbURL, function (response) {
pins = response;
$.each(pins, function (i, item) {
var _location = new Microsoft.Maps.Location(item.lat, item.lng);
var pin = new Microsoft.Maps.Pushpin(_location, { description: i, icon: markerImgURL, height: markerImgHeight, width: markerImgWidth, draggable: false });
Microsoft.Maps.Events.addHandler(pin, 'click', showDetails);
map.entities.push(pin);
});
});
I've added a click handler for each pin, so that when a user clicks (or touches) a pin, a window slides in with detailed information about it.
This is my showDetails() function:
function showDetails() {
var details = $("#details");
details.animate({
"right": parseInt(details.css("right")) == 0
? -1 * details.innerWidth() - 5
: 0
}, 500)
}
My problem is, that I don't really know how can I access the data of that individual pin from this function. I've added an id as the description of the pin, this would reference to the "pins" array where the full data is saved, but if I call the function like showDetails(i) inside my $.each() loop, the application would freeze at startup.
tl;dr: How can I access the description property of a pushpin from an external function without passing an id to the function?
Thanks!
Your showDetails() event handler will be passed the event object as a parameter. If you update your function signature to accept this event arg, you can access the target object which will be a reference to the pushpin. From there you can access the description and other properties
function showDetails(e) {
var details = $("#details");
var smokeShop = e.target;
WinJS.Utilities.setInnerHTML(details, smokeShop.description);
details.animate({
"right": parseInt(details.css("right")) == 0
? -1 * details.innerWidth() - 5
: 0
}, 500)
}
I have a set view to start on when loading the Google Earth API from my website, but it starts from space and then zooms in rather than starting on this zoomed-in view. This is wonky for the viewer particularly because my view is from the north looking south, so the Earth does a whirl on the way in:
http://www.colorado.edu/geography/cartpro/cartography2/fall2011/bartel/projects/project3_tungurahua/tungurahua_hazards.html
The map loads into an iframe. I'd like to toggle between various kmls without changing the zoomed view, as well, but I'll post that question separately. I've looked around for answers but haven't found anything specific to this--if I missed a post about this, I'm happy to check it out if someone can point me in the right direction.
Here's the code:
var ge;
google.load("earth", "1");
function init() {
google.earth.createInstance('map3d', initCB, failureCB);
}
function initCB(instance) {
ge = instance;
ge.getWindow().setVisibility(true);
// set navigation controls
ge.getNavigationControl().setVisibility(ge.VISIBILITY_AUTO);
// to fetch a KML file and show it
function finished(object) {
if (!object) {
// wrap alerts in API callbacks and event handlers
// in a setTimeout to prevent deadlock in some browsers
setTimeout(function() {
alert('Bad or null KML.');
}, 0);
return;
}
ge.getFeatures().appendChild(object);
var la = ge.createLookAt('');
la.set(-1.251336, -78.443817, 7000, ge.ALTITUDE_RELATIVE_TO_GROUND,
177, 65, 500);
ge.getView().setAbstractView(la);
}
//var marker = new GMarker(new GLatLng(-1.402002,-78.409471)); // latitude, longitude
// map.addOverlay(marker);
function failureCB(errorCode) {
}
google.setOnLoadCallback(init);
Thanks!!
You can set the fly to speed to SPEED_TELEPORT before loading the abstract view.
This setting will make globe instantaneously fly-to the desired location rather than 'swooping in'. If regular movement should be restored then after you have your initial view you can set the speed back to the default setting.
For example the following function can be used to instantaneously fly-to the desired location.
The method accepts any KmlAbstractView, i.e. a KmlCamera or KmlLookAt as its single parameter.
// fly-to a view using SPEED_TELEPORT
function teleport(abstractView) {
var oldSpeed = ge.getOptions().getFlyToSpeed(); .
ge.getOptions().setFlyToSpeed(ge.SPEED_TELEPORT);
ge.getView().setAbstractView(abstractView); // Wooosh!
ge.getOptions().setFlyToSpeed(oldSpeed);
}
Further to this to make sure that the transition is not shown at all for an initial position you can make the teleport move before setting the GEWindow visibility to True. For example.
function initCB(instance) {
ge = instance;
var la = ge.createLookAt('');
la.set(-1.251336, -78.443817, 7000, ge.ALTITUDE_RELATIVE_TO_GROUND, 177, 65, 500);
teleport(la); // set the position
ge.getWindow().setVisibility(true); // now display the window
//etc..