Create Elevation profile from polyline coordinate array - javascript

I have created a polyline using a coordinate array with code adapted from
https://google-developers.appspot.com/maps/documentation/javascript/examples/polyline-simple
Although the first (and probably worst) method to make the line was just a huge list of lat/lng points. Still learning the programming tricks, I apologize. Im a geographer not a programmer!
I want to get the elevation from that line and create an elevation profile graph.
Im new to JS and not sure how to debug whats not working. I cant seem to populate the path array with the coordinates from the polyline.
Its currently set to push the bikeCourseCoordinates to a new array that will then be used as a path. I tried it just using the bikeCourseCoordinates array as the 'path' but that didnt work either.
Online (but not working version) here:
http://geography.uoregon.edu:50000/bentesting/map_try3.html
function drawPath() {
// Create a new chart in the elevation_chart DIV.
chart = new google.visualization.ColumnChart(document.getElementById('elevation_chart'));
var path = new Array;
path.push(bikeCourseCoordinates);
// Create a PathElevationRequest object using this array.
var pathRequest = {
'path': path,
'samples': 256
}
// Initiate the path request.
elevator.getElevationAlongPath(pathRequest, plotElevation);
}
// Takes an array of ElevationResult objects, draws the path on the map
// and plots the elevation profile on a Visualization API ColumnChart.
function plotElevation(results, status) {
if (status == google.maps.ElevationStatus.OK) {
elevations = results;
// Extract the elevation samples from the returned results
// and store them in an array of LatLngs.
var elevationPath = [];
for (var i = 0; i < results.length; i++) {
elevationPath.push(elevations[i].location);
}
// Extract the data from which to populate the chart.
// Because the samples are equidistant, the 'Sample'
// column here does double duty as distance along the
// X axis.
var data = new google.visualization.DataTable();
data.addColumn('string', 'Sample');
data.addColumn('number', 'Elevation');
for (var i = 0; i < results.length; i++) {
data.addRow(['', elevations[i].elevation]);
}
// Draw the chart using the data within its DIV.
document.getElementById('elevation_chart').style.display = 'block';
chart.draw(data, {
width: 640,
height: 200,
legend: 'none',
titleY: 'Elevation (m)'
});
}
}

Are you trying to reproduce the third exemple on that page ? https://developers.google.com/maps/customize
If yes i have done it but without the graph over effects
here is my code
this goes in your head
script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"
script type="text/javascript" src="https://www.google.com/jsapi"
script src="https://www.google.com/uds/?file=visualization&v=1&packages=columnchart" type="text/javascript"
and this in the footer just before body tag
var elevator;
var map;
var chart;
var infowindow = new google.maps.InfoWindow();
var polyline;
var mapCenter = new google.maps.LatLng(-21.745585792425,165.91141052497);
// Load the Visualization API and the columnchart package.
google.load("visualization", "1", {packages: ["columnchart"]});
function initialize() {
var mapOptions = {
center: mapCenter,
zoom: 13,
mapTypeId: 'terrain'
};
map = new google.maps.Map(document.getElementById("map"), mapOptions);
// Create an ElevationService.
elevator = new google.maps.ElevationService();
// Draw the path, using the Visualization API and the Elevation service.
drawPath();
}
function drawPath() {
// Create a new chart in the elevation_chart DIV.
chart = new google.visualization.ColumnChart(document.getElementById('elevation-chart'));
var path = bikeCourseCoordinates;
// Create a PathElevationRequest object using this array.
// Ask for 256 samples along that path.
var pathRequest = {
'path': path,
'samples': 256
}
// Initiate the path request.
elevator.getElevationAlongPath(pathRequest, plotElevation);
}
function plotElevation(results, status) {
if (status == google.maps.ElevationStatus.OK) {
elevations = results;
// Extract the elevation samples from the returned results
// and store them in an array of LatLngs.
var elevationPath = [];
for (var i = 0; i < results.length; i++) {
elevationPath.push(elevations[i].location);
}
// Display a polyline of the elevation path.
var pathOptions = {
path: elevationPath,
strokeColor: '#0000CC',
opacity: 0.9,
map: map
}
polyline = new google.maps.Polyline(pathOptions);
// Extract the data from which to populate the chart.
// Because the samples are equidistant, the 'Sample'
// column here does double duty as distance along the
// X axis.
var data = new google.visualization.DataTable();
data.addColumn('string', 'Sample');
data.addColumn('number', 'Elevation');
for (var i = 0; i < results.length; i++) {
data.addRow(['', elevations[i].elevation]);
}
// Draw the chart using the data within its DIV.
document.getElementById('elevation-chart').style.display = 'block';
chart.draw(data, {
width: 960,
height: 300,
legend: 'none',
titleY: 'Elevation (m)'
});
}
}
initialize();

I couldn't find the problem but here are some observations which might help:
I tried it just using the bikeCourseCoordinates array as the 'path'
According to the Maps API, pathRequest should be be:
var pathRequest = {
'path': bikeCourseCoordinates,
'samples': 256
}
Further, I think the following initial part, ie:
var whitney = new google.maps.LatLng(36.578581, -118.291994);
...
...
var panamintsprings = new google.maps.LatLng(36.339722, -117.467778);
var badwater = new google.maps.LatLng(36.23998, -116.83171);
var bikeCourseCoordinates = [
new google.maps.LatLng(47.67609435030702, -116.7896032333374),
which comes directly within the first inline <script> tag should be called only after the maps library is loaded. I would put it all into another function say myInit and then call myInit from within your current function named initialize
The reason for this above is that although you are including the script tag to include the maps api maps.googleapis.com/maps/api/js?sensor=false the browser will continue to execute the next script block containing whitney= new google.maps.Lat because maps.googleapis.com is an external script and I think these external scripts are loaded asynchronously.

Related

How do I update popups on Azure Maps when re-rendering my map?

I'm currently working on an internal CRM application for my company. Its built with Blazor, and utilizes the Azure Maps javascript SDK. I'm using Azure Maps to visualize Map Items collected by player's of my company's game. All of the map setup is done in javascript according to the documentation provided by Microsoft.
The map has a symbol layer for displaying points, and a line layer for rendering the path traveled by a given player. Additionally, we are generating a summary of the player's collection stats (speed, distance, and time between collections). The page this component lives on has a DateRangePicker, a drop down menu of different item types, and a button to run the query. Currently, users of this web page can change the date range and the item type and rerun the query. This causes the Summary as well as the map with all points and the line to be re-rendered correctly. However, when the map is re-rendered, the popups attached to the points are no longer on the map, or are at least unresponsive to mouse events. If anyone has any ideas on how to keep and update my popups across map renderings, they would be greatly appreciated.
I was able to solve for this problem disposing of any map resources before attempting to create and render another map. This ensures that any html from previous map renders do not get in the way of the current map.
window.adminMap = (() => {
let map;
let datasource;
let point;
let popup;
const setupItemsMap = (subscriptionKey, mapItems) => {
if (map != null) {
map.dispose();
}
map = new atlas.Map('itemsMap', {
center: [mapItems[0].coordinate.long, mapItems[0].coordinate.lat],
centerOffest: [0, 0],
zoom: 15,
pitch: 90,
style: 'road',
language: 'en-US',
view: 'Auto',
authOptions: {
authType: 'subscriptionKey',
subscriptionKey: subscriptionKey
}
});
map.events.add('ready', () => {
//Create a data source and add it to the map.
datasource = new atlas.source.DataSource(null, {
lineMetrics: true
});
map.sources.add(datasource);
var points = [];
mapItems.forEach(function (mapItem) {
point = new atlas.data.Feature(new atlas.data.Point([mapItem.coordinate.long, mapItem.coordinate.lat]), {
collectedAt: mapItem.collectedAt,
speed: 0
});
points.push(point);
});
points.forEach(function (point) {
let previous;
let current = points.indexOf(point);
if (current > 0) {
previous = current - 1;
point.properties.speed = atlas.math.getSpeedFromFeatures(points[previous], points[current], "collectedAt", "milesPerHour", 1);
}
else {
point.properties.speed = 0;
}
});
datasource.add(points);
var line = createLineFrom(points);
datasource.add(line);
//Calculate a color gradient expression based on the speed of each data point.
var speedGradient = calculateGradientExpression(points, line);
//Create a line layer and pass in a gradient expression for the strokeGradient property.
map.layers.add(new atlas.layer.LineLayer(datasource, null, {
strokeWidth: 5,
strokeGradient: speedGradient
}));
//Create a layer to render each data point along the path.
var pointLayer = new atlas.layer.SymbolLayer(datasource, null, {
//Only render point data in this layer, not the points of the line.
filter: ['==', ['geometry-type'], 'Point']
});
//Create a popup. *This needs to update with the rest of the map!
popup = new atlas.Popup();
popup.open(map);
map.layers.add(pointLayer);
map.events.add('click', pointLayer, pointClicked);
});
}
Glad to here you resolved your issue and posted the answer for others. One recommendation, when ever possible it is best to reuse the map instance and layers. You could run the code above once in your app, then in the future use datasource.setShapes and pass in the new points. This would automatically update the data on the map. If the popup is open when adding new data, simply close it. It also looks like some optimizations can be done around your point conversion code/speed calculations. Here is a modified version of your code that may provide some performance enhancements.
window.adminMap = (() => {
let map;
let datasource;
let point;
let popup;
let lineLayer;
const setupItemsMap = (subscriptionKey, mapItems) => {
if (map != null) {
map.dispose();
}
map = new atlas.Map('itemsMap', {
center: [mapItems[0].coordinate.long, mapItems[0].coordinate.lat],
centerOffest: [0, 0],
zoom: 15,
pitch: 90,
style: 'road',
language: 'en-US',
view: 'Auto',
authOptions: {
authType: 'subscriptionKey',
subscriptionKey: subscriptionKey
}
});
map.events.add('ready', () => {
//Create a data source and add it to the map.
datasource = new atlas.source.DataSource(null, {
lineMetrics: true
});
map.sources.add(datasource);
lineLayer = new atlas.layer.LineLayer(datasource, null, {
strokeWidth: 5,
//strokeGradient: speedGradient
});
//Create a line layer and pass in a gradient expression for the strokeGradient property.
map.layers.add(lineLayer);
//Create a layer to render each data point along the path.
var pointLayer = new atlas.layer.SymbolLayer(datasource, null, {
//Only render point data in this layer, not the points of the line.
filter: ['==', ['geometry-type'], 'Point']
});
//Create a popup. *This needs to update with the rest of the map!
popup = new atlas.Popup();
popup.open(map);
map.layers.add(pointLayer);
map.events.add('click', pointLayer, pointClicked);
//Add mapItems to data source.
setMapItems(mapItems);
});
}
const setMapItems = (mapItems) => {
let points = [];
let previous;
let point;
for(let i = 0, len = mapItems.length; i < len; i++){
point = new atlas.data.Feature(new atlas.data.Point([mapItem.coordinate.long, mapItem.coordinate.lat]), {
collectedAt: mapItem.collectedAt,
speed: 0
});
if(previous){
point.properties.speed = atlas.math.getSpeedFromFeatures(previous, point, "collectedAt", "milesPerHour", 1);
}
points.push(point);
previous = point;
}
var line = createLineFrom(points);
//Calculate a color gradient expression based on the speed of each data point.
lineLayer.setOptions({
strokeGradient: calculateGradientExpression(points, line)
});
points.push(line);
//Add data to data source using setShapes. This clears and adds the data and only triggers one re-render of the map.
datasource.setShapes(points);
if(popup){
popup.close();
}
}
}

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();.

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);

javascript google maps v3 app help - order of instructions

Basically I'm making a nice and simple mobile web app for a couple of my friends. It uses some online databases to store position data of shops. I've got the databases working like a charm. No problems there. In fact everything is working except it's all happening in the wrong order I think. The data from the database should be stored in an array and then the objects in that array are displayed on screen. However, using some console logs I've found that the data is being displayed, then being retrieved from the database, then the arrays are filled. But no matter what I do, I can't get it to work! Here is my code:
var latOfSpots;
var lngOfSpots;
var nameOfSpots;
var spotArray;
var spotLatLng;
var spotCollection;
var markers;
var Spot;
var spot;
function init() {
//-------------------------- INITIATE SPOT VARIABLES ---------------------------//
map = new google.maps.Map2(document.getElementById("map"));
latOfSpots= new Array(51.14400,51.02295);
lngOfSpots= new Array(0.25721,0.26450);
nameOfSpots= new Array('Tescos', 'Sainsburys');
spotLatLng= new Array();
markers= new Array();
Spot = Parse.Object.extend("Spot");
spot = new Spot();
//----------------- GET DATA FROM THE PARSE.COM DATABASE ---------------------//
//---------------------- DISPLAY ARRAY DATA ON MAP ---------------------------//
GetData();
DisplayData();
//----------------------- SET MAP SETTINGS -----------------------------------//
map.setCenter(spotLatLng[0],8);
//map.addControl(new google.maps.LargeMapControl());
map.addControl(new google.maps.MapTypeControl());
}; //END OF INIT FUNCTION ------------------------------------------------//
google.setOnLoadCallback(init);
//------------------- PRIMARY FUNCTION TO GET DATA FROM DATABASE ---------------//
function GetData()
{
var query = new Parse.Query(Spot);
spotCollection = query.collection();
spotCollection.fetch({
success: function(spotCollection) {
// spotCollection.toJSON()
// will now be an array of objects based on the query
FillArrays();
console.log('data retreived' + spotCollection);
}
});
}
//----------------- FUNCTION TO LOAD DATABASE INTO ARRAYS -------------------//
function FillArrays()
{
spotArray = spotCollection.toJSON();
for (var j = 0; j<spotArray.length; j++)
{
latOfSpots.push(spotArray[j].Latitude);
lngOfSpots.push(spotArray[j].Longitude);
nameOfSpots.push(spotArray[j].Name);
}
}
//------------------------ FUNCTION TO DISPLAY ALL ARRAY DATA ONSCREEN -----------------//
function DisplayData()
{
for(var i = 0; i<latOfSpots.length; i++)
{
spotLatLng[i] = new google.maps.LatLng(latOfSpots[i], lngOfSpots[i]);
for(var x = 0; x<latOfSpots.length; x++)
{
markers[x] = new google.maps.Marker(
spotLatLng[i], {
"draggable":false,
"title":nameOfSpots[i],
});
map.addOverlay(markers[x]);
}
}
console.log('data displayed');
}
Your database query is asynchronous. You need to use the data in the Get_Data callback function (after it has come back from the server). Currently you are attempting to use it before the server sends it back.
//------------------- PRIMARY FUNCTION TO GET DATA FROM DATABASE ---------------//
function GetData()
{
var query = new Parse.Query(Spot);
spotCollection = query.collection();
spotCollection.fetch({
success: function(spotCollection) {
// spotCollection.toJSON()
// will now be an array of objects based on the query
FillArrays();
console.log('data retreived' + spotCollection);
DisplayData();
}
});
}

Categories