I'm trying to add marker objects to an array of markers with the Google Maps API but when I try to access the marker objects they are empty - javascript

I'm trying to assign the marker objects that I have added to my map into an array of markers to use in order to calculate the distances to each marker from a specific location. I begin with converting addresses from a text file that is fetched with a callback method that loops through each address and for each iteration sends a request to get the coordinates from the Geolocation API. In the geocode function, each request callback I add the corresponding LatLng to a new instance of a marker that is added to my map. Next, I try to push the marker onto my markers array.
When I try to console.log the values of the marker object through a button with an event listener, only empty objects are displayed.
I tried to declare the markers[] variable as a global variable. I'm still a beginner with Javascript but I think my issue may lie with the asynchronous action of the geocode request?
I get empty objects when I console log the markers array, although I get correct number of array elements (10 markers)
Here is my code:
let map;
let geocoder;
let markers = [];
window.onload = function() {
convertAddress();
document.getElementById('find_aed').addEventListener("click", function() {
let length = markers.length;
let aMarker;
for (let i = 0; i < length; i++) {
aMarker = markers[i]
console.log(aMarker.getPosition()); // ** This is where I print values **
}
});
}
function initMap() {
map = new google.maps.Map(document.getElementById("map"), {
zoom: 15,
center: { lat: 43.5327, lng: -80.2262 },
});
geocoder = new google.maps.Geocoder();
convertAddress();
const currLocation = new google.maps.LatLng(43.5327, -80.2262); object
console.log(currLocation.toString());
}
// convert address to latlng and add marker to map
function geocode(request) {
geocoder
.geocode(request)
.then((result) => {
const { results } = result;
// Add marker to map
let marker = new google.maps.Marker({
position: results[0].geometry.location,
map: map,
});
markers.push(marker); // ** This is where I push values to array **
return results; // return results
})
.catch((e) => {
alert("Geocode not successful for location:" + request + e);
})
}
// convert addresses into coordinates through google API request
function convertAddress() {
fetch('guelph_add.txt')
.then(response => response.text())
.then((text) => {
let addresses = text.split('\n');
let length = addresses.length;
let result;
console.log(length);
for (let i = 0; i < 10 ; i++) {
result = geocode ( {address: addresses[i]} );
}
});
}
window.initMap = initMap;

Related

How can I fetch data points belonging to a map bounding box?

I am plotting thousands of data points on a map, and at any point I want to fetch the data points falling in a particular map bounding box. For example, attached below is the complete view of the available data points,
If I zoom in a bit, i will have fewer points and I want to fetch the data associated with those points.
How I am approaching this is fetching the bounding box of the map, and then checking the coordinates of each point if they belong in that bounding box. Sample code is below,
const data = [
{lat: 33.93911, lng: 67.709953},
{lat: -11.2027, lng: 17.8739},
{lat: -33.8688, lng: 151.2093},
{lat: -42.8821, lng: 147.3272},
{lat: 26.0275, lng: 50.55}
]
const boundingBox = window.map.getMapBounds();
const result = [];
for(let i = 0; i < data.length; i++) {
if(/* point lies within bounding box */) {
result.push(point);
}
}
How can I optimise it or is there a better solution? Thanks a lot.
You can try using bbox-polygon and boolean-intersects from turf:
Use onViewStateChange from deck instance within a debounce function so it won't be executed too often
Create a polygon from the deck viewState using #turf/bbox-polygon
Pass it to #turf/boolean-intersects with the whole features using filter JS method
import { WebMercatorViewport } from '#deck.gl/core';
import bboxPolygon from '#turf/bbox-polygon';
import intersects from '#turf/boolean-intersects';
function debounce(fn, ms) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
fn.apply(this, args);
}, ms);
};
}
function getViewport(viewState) {
const bounds = new WebMercatorViewport(viewState).getBounds()
return bboxPolygon(bounds)
}
function getViewportFeatures({ viewState }) {
const viewport = getViewport(viewState)
const viewportFeatures = features.filter((f) => intersects(f, viewport))
console.log(viewportFeatures) // Your target
}
<DeckGL
...
onViewStateChange={debounce(getViewportFeatures, 500)}
/>
Advanced: if your data is really big, you can use Web Workers

Trouble getting custom pin to show in Bing Map

I'm showing using a bing map on my web page and I'm trying to use a custom pin for the location but it won't show up. My project is ASP.Net Core, My image is stored in wwwroot/images and this JavaScript is in wwwroot/js/site.js. I'm not sure if my path is just wrong or what.
var renderRequestsMap = function (divIdForMap, requestData) {
if (requestData) {
var bingMap = createBingMap(divIdForMap);
addRequestPins(bingMap, requestData);
}
}
function createBingMap(divIdForMap) {
var map = new Microsoft.Maps.Map(
document.getElementById(divIdForMap), {
credentials: BingMapKey,
zoom: 2
});
// tile url from Iowa Environmental Mesonet of Iowa State University
var urlTemplate = 'https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0q-{timestamp}/{zoom}/{x}/{y}.png';
var timestamps = ['900913-m50m', '900913-m45m', '900913-m40m', '900913-m35m', '900913-m30m', '900913-m25m', '900913-m20m', '900913-m15m', '900913-m10m', '900913-m05m', '900913'];
var tileSources = [];
for (var i = 0; i < timestamps.length; i++) {
var tileSource = new Microsoft.Maps.TileSource({
uriConstructor: urlTemplate.replace('{timestamp}', timestamps[i])
});
tileSources.push(tileSource);
}
var animatedLayer = new Microsoft.Maps.AnimatedTileLayer({ mercator: tileSources, frameRate: 500 });
map.layers.insert(animatedLayer);
return map;
}
function addRequestPins(bingMap, requestData) {
var locations = [];
$.each(requestData, function (index, data) {
var location = new Microsoft.Maps.Location(data.lat, data.long);
locations.push(location);
var order = index + 1;
alert(data.pinurl);
var pin = new Microsoft.Maps.Pushpin(location, { icon: 'images/low-risk-south-pin.png' });
bingMap.entities.push(pin);
});
var rect = Microsoft.Maps.LocationRect.fromLocations(locations);
bingMap.setView({ bounds: rect, padding: 80 });
}
Also when the map loads it is super zoomed into my pin and whatever I do I can't get it to start with a far out zoom.
If you can, take a look at the network trace. Here you should see where the images are being requested from and will help you verify if it is requesting from the right location.
As for the zooming, you are calculating in the LocationRect from the locations of the pins and then setting the map view based on that. Sounds like that is working as expected. If you don't want to zoom in on your pins, remove that code.

Browser freezes when clearing layers

I have a map where I need to show some locations based on the year the has selected. There are about 100k locations per year.
Everything works fine during the first load (year 2015). But if the user chooses another year (let's say year 2016), the browser will freeze.
Below is my code. I am callin markers.clearLayers() in order to remove the current points (from year 2015) and update the map with the new ones (year 2016).
var map = L.map('map', {
center: latlng,
zoom: 10,
layers: [tiles]
});
var markers = L.markerClusterGroup({
chunkedLoading: true
});
fetchLocations(yearSlider.value)
function fetchLocations(year) {
markers.clearLayers();
fetch('http://localhost:3003/locations/year/' + year)
.then(response => response.json())
.then(json => {
for (var i = 0; i < json.length; i++) {
const geo = json[i].geo;
const coords = geo.coordinates;
var marker = L.marker(L.latLng(coords[1], coords[0]), {
title: "abc"
});
marker.bindPopup("abc");
markers.addLayer(marker);
}
map.addLayer(markers);
});
}
First way is to reduce the visible markers for just what is on screen. This will increase a lot of performance.
var markers = new L.MarkerClusterGroup(....);
// Remove everything outside the current view (Performance)
markers._getExpandedVisibleBounds = function () {
return mcg._map.getBounds();
};
Second way is increasing maxClusterRadius will give you less clusters over the map. This also improve dragging performance.
Other freezing issue might be creating L.marker(...) of 100k locations.
Editted:
fetchLocations(yearSlider.value)
var map;
function initMap () {
// Reset whole map and redraw it.
if (map) {
map.off();
map.remove();
}
// Create a new leaflet map
map = L.map('map', {
center: latlng,
zoom: 10,
layers: [tiles]
});
return;
}
function fetchLocations(year) {
initMap();
fetch('http://localhost:3003/locations/year/' + year)
.then(response => response.json())
.then(json => {
// Create new marker cluster group
var markers = L.markerClusterGroup({
chunkedLoading: true
});
for (var i = 0; i < json.length; i++) {
const geo = json[i].geo;
const coords = geo.coordinates;
var marker = L.marker(L.latLng(coords[1], coords[0]), {
title: "abc"
});
marker.bindPopup("abc");
markers.addLayer(marker);
}
// Add individual group to the map
map.addLayer(markers);
});
}
The issue is that you are individually adding Markers to your Cluster Group while the latter is already on map:
function () {
// On next calls, `markers` is still on map
for (var i = 0; i < json.length; i++) {
markers.addLayer(marker); // synchronously processes when the group is on map
}
map.addLayer(markers); // on first call, `markers` is not yet on map
}
You can accumulate your markers in an array within your loop, then once it is full, you use markers.addLayers(arrayOfMarkers)
Or remove your markers group from map at the beginning of your function, so that it internally accumulates your Markers and batch processes them when it is added on map at the end.
Even though parndepu's solution also works, I managed to solve the problem by simply adding map.removeLayer(markers); after markers.clearLayers();.

Conditionally apply variable if data is present in JavaScript

Probably simple answer for someone that knows JavaScript, I don't know it very well. I'm getting JSON data back and applying markers to a map. However it's generating markers for those that have null which really messes things up. So what I need to do is create a conditional variable based on data being present. I have the following in the code:
let mapElement = document.getElementById('map-banner');
let pointMarkers = mapElement.getAttribute('data-location');
let marked = JSON.parse(pointMarkers);
let bounds = new google.maps.LatLngBounds();
console.log(pointMarkers);
marked.forEach(marked => {
if (marked.lat > 0 && marked.lat !== null) {
let lat = marked.lat;
}
let lng = marked.lng;
const marker = new google.maps.Marker({
position: new google.maps.LatLng(lat, lng),
map: map,
icon: '/marker.png',
title: marked.name
});
bounds.extend(marker.position);
});
map.fitBounds(bounds);
};
Specifically I'm working with the variables lat and lng. I've also tried:
let lat = marked.lat;
if (lat > 0 && lat !== null) {
const lat = marked.lat;
}
In this case it presents all the data and it doesn't appear to be applying the condition.
You are conditionally declaring the variable, which for javascript is optional.
What you want is to skip that iteration in your loop with a guard clause:
marked.forEach(marked => {
if (marked.lat == null)
return;
const marker = new google.maps.Marker({
position: new google.maps.LatLng(marked.lat, marked.lng),
map: map,
icon: '/marker.png',
title: marked.name
});
bounds.extend(marker.position);
});
I think a filter is what you're looking for. Filtering can remove entries from arrays which you don't want to use.
Docs (by MDN) -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
Also, const/let are used to achieve block scope in JavaScript.
consts are used for variables that do not change in value and are (preferably) immutable, see Docs -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
lets are used for values that do change in value, and have different values in different block scopes, see Docs -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
const mapElement = document.getElementById('map-banner');
const pointMarkers = mapElement.getAttribute('data-location');
// catch any error from JSON.parse
try {
const coords = JSON.parse(pointMarkers);
const bounds = new google.maps.LatLngBounds();
// filter out incorrect coords
const markers = coords.filter(coord => {
return (marked.lat > 0 && marked.lat !== null)
});
// create markers, extend bounds
markers.forEach(({ lat, lng }) => {
const marker = new google.maps.Marker({
position: new google.maps.LatLng(lat, lng),
map,
icon: '/marker.png',
title: marked.name
});
bounds.extend(marker.position);
});
// fit bounds
map.fitBounds(bounds);
} catch (error) {
// handle error to keep users happy
}
Maybe just show the marker if it exists?:
if(marked.lat && marked.lat > 0) {
const marker = new google.maps.Marker({
position: new google.maps.LatLng(lat, lng),
map: map,
icon: '/marker.png',
title: marked.name
});
}

Mapbox dynamic markers in Meteor.js

Able to successfully set the mapbox viewpoint dynamically by passing the geocoder a street address stored in my database.
But rather than just setting the map view to the address, I want to draw a marker at the address' location.
Template.vendorPage.rendered = function(){
//get address from database by ID
address = function(){
pathname =location.pathname.split("/");
thisId = pathname[2];
return Vendors.findOne({_id: thisId}).address
}
//set variable to the address function
thisAddress = address();
//draw the mapbox
L.mapbox.accessToken = '<My Token Here>';
var geocoder = L.mapbox.geocoder('mapbox.places-v1'),
map = L.mapbox.map('map', 'alexnetsch.j786e624');
geocoder.query(thisAddress, showMap);
function showMap(err, data) {
// The geocoder can return an area, like a city, or a
// point, like an address. Here we handle both cases,
// by fitting the map bounds to an area or zooming to a point.
if (data.lbounds) {
map.fitBounds(data.lbounds);
} else if (data.latlng) {
map.setView([data.latlng[0], data.latlng[1]], 16);
}
}
}
Played around with the documentation for hours and can't figure it out. I'd like to simply pass the marker function 'thisAddress'
Seems like rather than setting the viewport, I could set the map to be zoomedin and centered around my marker.
Here is the example from the documentation but without Geocoding the location.
L.mapbox.accessToken = 'pk.eyJ1IjoiYWxleG5ldHNjaCIsImEiOiJsX0V6Wl9NIn0.i14NX5hv3bkVIi075nOM2g';
var map = L.mapbox.map('map', 'examples.map-20v6611k')
.setView([38.91338, -77.03236], 16);
L.mapbox.featureLayer({
// this feature is in the GeoJSON format: see geojson.org
// for the full specification
type: 'Feature',
geometry: {
type: 'Point',
// coordinates here are in longitude, latitude order because
// x, y is the standard for GeoJSON and many formats
coordinates: [
-77.03221142292,
38.913371603574
]
},
properties: {
title: 'Peregrine Espresso',
description: '1718 14th St NW, Washington, DC',
// one can customize markers by adding simplestyle properties
// https://www.mapbox.com/foundations/an-open-platform/#simplestyle
'marker-size': 'large',
'marker-color': '#BE9A6B',
'marker-symbol': 'cafe'
}
}).addTo(map);
Figured it out finally.
Template.vendorPage.rendered = function(){
address = function(){
pathname =location.pathname.split("/");
thisId = pathname[2];
return Vendors.findOne({_id: thisId}).address
}
thisAddress = address();
//draw the mapbox
L.mapbox.accessToken = 'pk.eyJ1IjoiYWxleG5ldHNjaCIsImEiOiJsX0V6Wl9NIn0.i14NX5hv3bkVIi075nOM2g';
var geocoder = L.mapbox.geocoder('mapbox.places-v1'),
map = L.mapbox.map('map', 'alexnetsch.j786e624');
geocoder.query(thisAddress, showMap);
function showMap(err, data) {
// The geocoder can return an area, like a city, or a
// point, like an address. Here we handle both cases,
// by fitting the map bounds to an area or zooming to a point.
if (data.lbounds) {
map.fitBounds(data.lbounds);
} else if (data.latlng) {
map.setView([data.latlng[0], data.latlng[1]], 16);
}
}
var addMarker;
addMarker = function(geocoder, map, placeName) {
return geocoder.query(placeName, function(error, result) {
var marker;
marker = L.marker(result.latlng);
return marker.addTo(map);
});
};
addMarker(geocoder, map, thisAddress);

Categories