Bing Maps V8 JS API Memory Leak Issues - javascript
Overview: This project I've been working on receives a message from an external source and updates a Bing Map based on the information received. However, when I let the page run for a long time, the page just ends up eating up all of the memory and crashing itself.
Specifics: This memory issue arose once I switched over to Bing Maps from Google Maps and I have not been able to resolve it no matter what I've tried. I've searched all over to try and either resolve this issue through Bing Maps best practices or at least find what's causing this issue, but I have been unable to find anything that actually fixed the issue.
I have a significant amount of detached DOMs and have tried to analyze them more closely using Google Chrome's console, but have been unsuccessful.
Attempted Fixes:
Moving everything to do with adding handlers into the map initialization function (as displayed below), but that did not seem to improve it by much.
Loading any Bing Maps modules from the map initialization function (as displayed below). This cleaned the code up quite a bit, but didn't seem to have much of an affect on the memory usage.
Loading the map synchronously. However, this seemed to break just about everything to do with Bing Maps on the site.
Stopped disposing of the Map upon receiving a message. This assisted in key usage, but did not help with the memory usage.
Relevant code:
Here is the map initialization function (run asynchronously by the Bing Maps call in the HTML):
function initialize () {
const CENTER = new Microsoft.Maps.Location(44.96375272262944, -93.2353971897461);
// Assigns the zoom depending on whether the device is a mobile device or not
if (isMobile()) {
zoom = 12;
} else {
zoom = 13;
}
// Initialize the map
map = new Microsoft.Maps.Map(document.getElementById('map-canvas'), {
credentials: API_KEY,
minZoom: zoom,
center: CENTER,
disableStreetside: true,
disableStreetsideAutoCoverage: true,
enableClickableLogo: false,
showLocateMeButton: false,
showMapTypeSelector: false
});
bus1 = {
assignment: null,
destination: null,
distance: null,
eta: null,
location: null,
mph: null,
name: null,
pin: new Microsoft.Maps.Pushpin(map.getCenter(), {
icon: 'img/bus1.png',
anchor: new Microsoft.Maps.Point(14, 44),
visible: false,
text: "",
title: ""
}),
polylineRender: null,
time: null,
timeout: null,
};
bus2 = {
assignment: null,
destination: null,
distance: null,
eta: null,
location: null,
mph: null,
name: null,
pin: new Microsoft.Maps.Pushpin(map.getCenter(), {
icon: 'img/bus2.png',
anchor: new Microsoft.Maps.Point(14, 44),
visible: false,
text: "",
title: ""
}),
polylineRender: null,
time: null,
timeout: null,
};
bus3 = {
assignment: null,
destination: null,
distance: null,
eta: null,
location: null,
mph: null,
name: null,
pin: new Microsoft.Maps.Pushpin(map.getCenter(), {
icon: 'img/bus3.png',
anchor: new Microsoft.Maps.Point(14, 44),
visible: false,
text: "",
title: ""
}),
polylineRender: null,
time: null,
timeout: null,
};
buses = [bus1, bus2, bus3];
// Add the traffic layer
Microsoft.Maps.loadModule('Microsoft.Maps.Traffic', function () {
trafficLayer = new Microsoft.Maps.Traffic.TrafficManager(map);
});
// Add the directions manager
Microsoft.Maps.loadModule('Microsoft.Maps.Directions', function () {
bus1.polylineRender = new Microsoft.Maps.Directions.DirectionsManager(map);
bus2.polylineRender = new Microsoft.Maps.Directions.DirectionsManager(map);
bus3.polylineRender = new Microsoft.Maps.Directions.DirectionsManager(map);
Microsoft.Maps.Events.addHandler(bus1.polylineRender, 'directionsError', function (e) {
console.log("Error: " + e.message + "\r\nResponse Code: " + e.responseCode);
});
Microsoft.Maps.Events.addHandler(bus1.polylineRender, 'directionsUpdated', directionsUpdated);
Microsoft.Maps.Events.addHandler(bus2.polylineRender, 'directionsError', function (e) {
console.log("Error: " + e.message + "\r\nResponse Code: " + e.responseCode);
});
Microsoft.Maps.Events.addHandler(bus2.polylineRender, 'directionsUpdated', directionsUpdated);
Microsoft.Maps.Events.addHandler(bus3.polylineRender, 'directionsError', function (e) {
console.log("Error: " + e.message + "\r\nResponse Code: " + e.responseCode);
});
Microsoft.Maps.Events.addHandler(bus3.polylineRender, 'directionsUpdated', directionsUpdated);
});
// Defines the polygons surrounding each campus
polygonArrSTP = [
new Microsoft.Maps.Location(44.94619673931851, -93.19240808486938),
new Microsoft.Maps.Location(44.941321471037966, -93.19249391555786),
new Microsoft.Maps.Location(44.94130628263941, -93.19764375686646),
new Microsoft.Maps.Location(44.93790398010943, -93.1975257396698),
new Microsoft.Maps.Location(44.937926764055824, -93.1924831867218),
new Microsoft.Maps.Location(44.94164802063501, -93.19241881370544),
new Microsoft.Maps.Location(44.94164802063501, -93.18739771842957),
new Microsoft.Maps.Location(44.94618914576464, -93.18735480308533),
new Microsoft.Maps.Location(44.94618914576464, -93.1924295425415),
];
polygonArrMPLS = [
new Microsoft.Maps.Location(44.97380025938377, -93.2795798778534),
new Microsoft.Maps.Location(44.97295018417148, -93.27883958816528),
new Microsoft.Maps.Location(44.97264658282772, -93.27782034873962),
new Microsoft.Maps.Location(44.973595331690625, -93.27698349952698),
new Microsoft.Maps.Location(44.9745744240603, -93.27614665031433),
new Microsoft.Maps.Location(44.97501463068608, -93.27712297439575),
new Microsoft.Maps.Location(44.9747205274961, -93.27738046646118),
new Microsoft.Maps.Location(44.974339139822895, -93.27832460403442),
new Microsoft.Maps.Location(44.97380025938377, -93.2795798778534)
];
// Adds the campus polygons to the map
polygonMPLS = new Microsoft.Maps.Polygon(polygonArrMPLS, {
fillColor: "rgba(255, 0, 0, 0.4)",
strokeColor: '#FF0000',
strokeThickness: 2
});
polygonSTP = new Microsoft.Maps.Polygon(polygonArrSTP, {
fillColor: "rgba(255, 0, 0, 0.4)",
strokeColor: '#FF0000',
strokeThickness: 2
});
// Assign the polygons to the Map
map.entities.push(polygonMPLS);
map.entities.push(polygonSTP);
// Set the toggle for advanced mode
advancedModeEnabled = false;
generateBusStats();
subscribeToPubnub();
console.log("Initialization complete.");
}
Here is the function that runs upon receiving a message:
function redraw(payload) {
// If the user is does not have the page active, the payload is refused
if (!acceptingPayloads) {
return false;
}
let location = new Microsoft.Maps.Location(payload.message.lat, payload.message.lng);
let name = payload.message.name;
let dest = payload.message.dest;
let mph = payload.message.mph;
const STP = new Microsoft.Maps.Location(44.9416428, -93.1917952);
const MPLS = new Microsoft.Maps.Location(44.9747502, -93.2774464);
if (dest.toUpperCase() === "S") {
dest = {letter: "S", name: "St. Paul", coords: STP};
} else if (dest.toUpperCase() === "M") {
dest = {letter: "M", name: "Minneapolis", coords: MPLS};
} else {
dest = null;
}
console.log(name + ": " + location.latitude + ", " + location.longitude + " - " + dest.name + " - " + mph + " mph");
// Gets the bus object that the payload was sent from
currentBus = getCurrentBus(name);
// Removes the timeout for the current bus
if (currentBus.timeout !== null) {
clearTimeout(currentBus.timeout);
}
currentBus.location = location;
currentBus.destination = dest;
currentBus.mph = mph;
currentBus.time = Date.now();
currentBus.name = name;
// Restart the timeout for the current bus
beginTimeout();
// Calculate the distance between the current bus and its destination
calcDistToDest();
$("." + currentBus.assignment + "-item").css('display', 'block')
}
Finally, here is the function that I use to get the distance between points:
function calcDistToDest() {
// Clear all information from the Directions Manager
currentBus.polylineRender.clearAll();
// Set Route Mode to driving and the render options
currentBus.polylineRender.setRequestOptions({
routeMode: Microsoft.Maps.Directions.RouteMode.driving
});
currentBus.polylineRender.setRenderOptions({
autoUpdateMapView: false,
drivingPolylineOptions: {
visible: POLYLINE_VISIBILITY
},
waypointPushpinOptions: {
visible: false
},
firstWaypointPushpinOptions: {
anchor: currentBus.pin.getAnchor(),
icon: currentBus.pin.getIcon(),
title: currentBus.pin.getTitle(),
text: currentBus.pin.getText()
}
});
// Sets the waypoint of the bus's current position and destination
currentBus.polylineRender.addWaypoint( new Microsoft.Maps.Directions.Waypoint({
location: currentBus.location
}));
currentBus.polylineRender.addWaypoint( new Microsoft.Maps.Directions.Waypoint({
location: currentBus.destination.coords
}));
// Calculate the directions
currentBus.polylineRender.calculateDirections();
}
Basically what I'm looking for is a way to find out what is causing this issue, an actual resolution to the issue if it's simple and I'm just missing something obvious, or the best practices to avoid this issue in the first place.
Note: My apologies for posting so much code. It's hard to determine what code to post because I don't really know what section of the code is causing the issue. Let me know if this needs to be modified or if any other information is needed and I would be happy to oblige. I also left out a lot of seemingly irrelevant JS code from the same file, I'd be happy to add it if need be.
The memory leaks were primarily resolved via the implementation of the experimental branch of Bing Maps as suggested by rbrundritt.
Here is an example of how to import Bing Maps using the experimental branch:
<script src='https://www.bing.com/api/maps/mapcontrol?branch=experimental&callback=[Insert callback function]' async defer></script>
I was later able to switch back to the release branch as the fixes for the memory leaks were pushed to that branch. More information regarding the Bing Maps Map Control Branches can be found here.
Related
Accessing Mapbox Geocoder Object
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]}`) }) )
Jsplumb detach connection
I am trying to detach a connection when widget is clicked. refers to the posts: Jsplumb remove all connections not working with verion 2.4.3 How to delete jsPlumb connection I am on newest CDNJS release 2.15.6, here's what I have tried: this.plumb.bind('click', function(conn){ connect.connections.splice(connect.connections.indexOf(conn.cn), 1); this.plumb.deleteConnection(conn); connect.render(); }); // jsPlumb: fire failed for event click : TypeError: undefined is not an object (evaluating 'this.plumb.deleteConnection') this.plumb.bind('click', function(conn){ connect.connections.splice(connect.connections.indexOf(conn.cn), 1); jsPlumb.deleteConnection(conn); connect.render(); }); this successfully delete the connection, however, since that I cannot add new connection any more if the target and sources corresponds to deleted connection. I got an error saying: TypeError: null is not an object (evaluating 'x.endpoints[0]') jsplumb.min.js refers to this issue, I tried to switch to version 5, but I did not find any browser script on https://github.com/jsplumb/jsplumb, by the way there are bunch of errors in the demo provided. I will be very grateful for any of following: method to detach a connection advice on any potential error on my conduction CDN for newest stable version minimum snippet to generate the error setTimeout(()=>{ let source = document.createElement('div'); let target = document.createElement('div'); document.body.appendChild(source); document.body.appendChild(target); let instance = jsPlumb.getInstance({ PaintStyle: { strokeWidth: 1 }, Container: document.body, }); function getConnection(){ return instance.connect({ source: source, target: target, cssClass: 'redLine', endpoint: "Blank", anchor:"AutoDefault", overlays: [ ["Arrow", { location: 1, width: 10, length: 10 } ], [ "Label", { location:0.5, label:"Label Text" } ], ], paintstyle: { lineWidth: 1, strokeStyle: 'black', } , connector: ["Flowchart"], }); } let conn = getConnection(); jsPlumb.deleteConnection(conn, { fireEvent: false, //fire a connection detached event? forceDetach: false //override any beforeDetach listeners }); conn = getConnection(); }, 0) now we are trying to solve the issue on Github
The problem here was that the deleteConnection method was being called on the default instance of jsPlumb but the rest of the code was using a specific instance: let instance = jsPlumb.getInstance({ PaintStyle: { strokeWidth: 1 }, Container: document.body, }); ... jsPlumb.deleteConnection(...) <-- should have been instance.deleteConnection
Google Places Autocomplete API error, "google is not defined"
I am scaffolding a simple prototype using google places and maps api using Vue. In index.html, I added the script tag, libraries, and api key, I get 200 response in the network tab. <script src="https://maps.googleapis.com/maps/api/js?key=My-api-key&libraries=places" async defer></script> In App.vue, I added the following <input ref="autocomplete" type="text" /> ... <script> export default { name: "App", data() { return { autocomplete: "", }; }, methods: {}, mounted() { const center = { lat: 40.84498856765032, lng: -73.71060855293794 }; // Create a bounding box with sides ~10km away from the center point const defaultBounds = { north: center.lat + 0.1, south: center.lat - 0.1, east: center.lng + 0.1, west: center.lng - 0.1, }; const originAutoComplete = google.maps.places.AutoComplete( this.$refs["autocomplete"], { bounds: defaultBounds, } ); originAutoComplete.addListener("place_changed", () => { console.log(originAutoComplete.getPlace()); }); How do I resolve this error? Is there a way to initialize this in App.vue script tag? The example google developers youtube works great, but I'd like to implement this in a Vue context. One other note, I tried adding window before the .google, no dice. UPDATE 1 as per your advice, I installed googlemaps api loader and in App.vue: <script> import { Loader } from "#googlemaps/js-api-loader"; const loader = new Loader({ apiKey: "YOUR_API_KEY", version: "weekly", libraries: ["places"], }); export default { name: "App", data() { return { autocomplete: "", }; }, methods: { printData() { console.log(this.$refs["autocomplete"].value); }, }, async mounted() { let origin; const center = { lat:40.84498856765032, lng:-73.71060855293794 }; // Create a bounding box with sides ~10km away from the center point const defaultBounds = { north: center.lat + 0.1, south: center.lat - 0.1, east: center.lng + 0.1, west: center.lng - 0.1, }; loader .load() .then((google) => { origin = new google.maps.places.Autocomplete( this.$refs["autocomplete"], { types: ["cities"], defaultBounds, fields: ["place_id", "geometry", "name"], } ); origin.addListener("place_changed", () => { console.log(origin.getPlace()); }); }) .catch((e) => { console.log(e); // do something }); Now this issue is the drop down of selections is not appearing.
The issue is that your Vue App JS is loading before the google maps JS. There are some options: Block your app loading on Google Maps by removing the async and defer from the script. Make sure the script tag is above your Vue JS after removing the async/defer. Incorporate a callback parameter or just check for existence of window.google in your vue component and allow vue to update when it is available. Use #googlemaps/js-api-loader in the Vue component. See Is `async/await` available in Vue.js `mounted`? for ways to incorporate. I recommend option 3.
How to setup tradingview charting library chart to update automatically?
I am setting up a website displaying chart using tradingview chart library, and managed to setup chart to display from data feed. However once chart is loaded, the chart is not auto updating or refreshing with newer data without reloading webpage. How do I setup the chart to update automatically (eg. interval 1m, 5m etc)? This is the code I used: function initOnReady() { var widget = window.tvWidget = new TradingView.widget({ // debug: true, // uncomment this line to see Library errors and warnings in the fullscreen: true, symbol: 'AAPL', interval: '1D', container_id: "tv_chart_container", // BEWARE: no trailing slash is expected in feed URL datafeed: new Datafeeds.UDFCompatibleDatafeed("<data feed url>"), library_path: "charting_library/", locale: getParameterByName('lang') || "en", disabled_features: ["use_localstorage_for_settings"], enabled_features: ["study_templates"], charts_storage_url: 'https://saveload.tradingview.com', charts_storage_url: 'http://{$smarty.server.HTTP_HOST}', charts_storage_api_version: "1.1", client_id: 'tradingview.com', user_id: 'public_user_id', }); }; Thanks in advance and appreciate for helps.
create file named datafeed like so: export default { onReady: (callback) => { console.log("[onReady]: Method call"); callback({}); }, searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => { console.log("[searchSymbols]: Method call"); }, resolveSymbol: ( symbolName, onSymbolResolvedCallback, onResolveErrorCallback ) => { console.log("[resolveSymbol]: Method call", symbolName); }, getBars: async ( symbolInfo, resolution, from, to, onHistoryCallback, onErrorCallback, firstDataRequest ) => { }, subscribeBars: ( symbolInfo, resolution, onRealtimeCallback, subscribeUID, onResetCacheNeededCallback ) => { console.log( "[subscribeBars]: Method call with subscribeUID:", subscribeUID ); }, unsubscribeBars: (subscriberUID) => { console.log( "[unsubscribeBars]: Method call with subscriberUID:", subscriberUID ); }, }; and replace it with datafeeds: import DATAFEED from './datafeed'; function initOnReady() { var widget = window.tvWidget = new TradingView.widget({ // debug: true, // uncomment this line to see Library errors and warnings in the fullscreen: true, symbol: 'AAPL', interval: '1D', container_id: "tv_chart_container", // BEWARE: no trailing slash is expected in feed URL datafeed: DATAFEED, // ---> replace here library_path: "charting_library/", locale: getParameterByName('lang') || "en", disabled_features: ["use_localstorage_for_settings"], enabled_features: ["study_templates"], charts_storage_url: 'https://saveload.tradingview.com', charts_storage_url: 'http://{$smarty.server.HTTP_HOST}', charts_storage_api_version: "1.1", client_id: 'tradingview.com', user_id: 'public_user_id', }); }; Notice : Trading view itself manage most actions base on what it needs. for example if you want to drag the candle chart, trading view calculate the view port and find out how many candle it need's to show then call getBars method in datafeeds.js. for see examples: https://github.com/tradingview/charting-library-examples
Error when add multiple waypoints to Leaflet
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);