The trouble with Charting with Google Earth Engine - charts

In Google Earth Engine, I want to get the NDVI index from several images of the Sentinel 2 satellite with different dates, then I will estimate other parameters from this index. To do this, I need to convert the resulting NDVI images to an image collection. But to chart it, it gives the following error:
Error generating chart: No features contain non-null values of "system:time_start".
It seems that when converting to a collection image, the temporal information of the images is lost. with this condition, how can I fix it?
Link to the code: https://code.earthengine.google.com/47cd9e7f65b143242ebb238d136bf760
Code:
var sentinel1 = ee.Image('COPERNICUS/S2_SR/20181214T072311_20181214T072733_T39SVV');
var sentinel2 = ee.Image('COPERNICUS/S2_SR/20181219T072319_20181219T072610_T39SVV');
var sentinel3 = ee.Image('COPERNICUS/S2_SR/20181224T072311_20181224T072313_T39SVV');
var ndvi1 = sentinel1.normalizedDifference(['B8','B4']);
var ndvi2 = sentinel2.normalizedDifference(['B8','B4']);
var ndvi3 = sentinel3.normalizedDifference(['B8','B4']);
var NDVI_COL = ee.ImageCollection.fromImages([ndvi1, ndvi2, ndvi3]);
var chart = ui.Chart.image.series(
NDVI_COL, geometry, ee.Reducer.mean(),10,'system:time_start');
print(chart);

Try this code
Make a collection of selected images, then plot
var sentinel1 = ee.Image('COPERNICUS/S2_SR/20181214T072311_20181214T072733_T39SVV');
var sentinel2 = ee.Image('COPERNICUS/S2_SR/20181219T072319_20181219T072610_T39SVV');
var sentinel3 = ee.Image('COPERNICUS/S2_SR/20181224T072311_20181224T072313_T39SVV');
var S2 = ee.ImageCollection([sentinel1, sentinel2, sentinel3])
.filterBounds(geometry)
.map(function(image){return image.clip(geometry)});
print('collection of Selected Images to plot: ', S2);
var addNDVI = function(image) {
var ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI');
return image.addBands(ndvi);
};
var S2_NDVI = S2.map(addNDVI);
var NDVI_S2 = ee.ImageCollection(S2_NDVI.select(["NDVI"], ["NDVI"]));
var chart =
ui.Chart.image
.seriesByRegion({
imageCollection: NDVI_S2,
band: 'NDVI',
regions: geometry,
reducer: ee.Reducer.mean(),
scale: 10,
xProperty: 'system:time_start'
})
.setOptions({
title: 'Average NDVI Value by Date',
hAxis: {title: 'Date', titleTextStyle: {italic: false, bold: true}},
vAxis: {
title: 'NDVI',
titleTextStyle: {italic: false, bold: true}
},
});
print(chart);

Related

How to dynamically create ticks (month) in google chart?

Given an aggregated data table that is defined as:
aggData: [Date: date][Team: string][Score: number]
I want to plot the aggregated data with the ability to filter by year. I am using dynamic ticks on the hAxis to avoid the repeating labels problem. However, the label for the custom ticks does not appear.
I want the hAxis to display the months. My hunch is I'm not creating the ticks properly
See images below
var hAxisTicks = [];
var dateRange = aggData.getColumnRange(0);
for (var y = dateRange.min.getFullYear(); y <= dateRange.max.getFullYear(); y = y + 1) {
for(var m = dateRange.min.getMonth(); m <= dateRange.max.getMonth(); m = m + 1){
hAxisTicks.push(new Date(y,m));
}
}
var yearPicker = new google.visualization.ControlWrapper({
controlType: 'CategoryFilter',
containerId: 'categoryFilter_div',
options: {
filterColumnIndex: 0,
ui: {
allowTyping: false,
allowMultiple: false,
label: 'Year:',
labelStacking: 'vertical'
},
useFormattedValue: true
}
});
var lineChart = new google.visualization.ChartWrapper({
chartType: 'LineChart',
containerId: 'chart_div',
options: {
width: 900,
height: 500,
hAxis: {
format: 'MMM',
ticks: hAxisTicks
}
}
});
aggData.sort([{ column: 0 }]);
// draw chart
var dashboard = new google.visualization.Dashboard(document.getElementById('dashboard_div'));
dashboard.bind(yearPicker, lineChart);
dashboard.draw(aggData);
<div id="dashboard_div">
<div id="categoryFilter_div"></div>
<div id="chart_div"></div>
</div>
When specifying the hAxisTicks value, the chart comes out without labels on the hAxis
Without specifying hAxisTicks the chart looks like:
i've logged the data to console using
google.visualization.dataTableToCsv(aggData)
the output is:
"Oct 1, 2019",128,0,0,0
"Nov 1, 2019",152,75,0,0
"Dec 1, 2019",0,0,23,0
"Jan 1, 2020",225,0,0,84
the issue with the for loop is in the month portion.
given the data you provided, the month for the min date = 9 (Oct)
however, the month for the max date = 0 (Jan)
so the month for loop does not run, because 9 > 0
instead, let's use a while loop.
var dateTick = dateRange.min;
while (dateTick.getTime() <= dateRange.max.getTime()) {
hAxisTicks.push(dateTick);
dateTick = new Date(dateTick.getFullYear(), dateTick.getMonth() + 1);
}

Changing overlay layers when switching base layer

I have built a leaflet map with two base layers, and each of these base layers will have their own unique points of interest. The points of interest are being stored as geojson that I loop over to create multiple overlays for different categories. So when viewing the default base layer you would see layers for Show All, Cat1, Cat2 etc.
I need a way to be able to attach overlay layers to a base layer, or remove all overlay layers and then load the relevant ones when the base layer changes.
I tried using the following, which worked to switch categories, with the baselayerchange event, but the overlay layers were still displaying when I switched base layers.
layerControl._layers.forEach(function(layer){
if(layer.overlay){
map.removeLayer(layer.layer)
}
});
I've been searching for a couple of days now for an answer to this without any luck, any help is greatly appreciated.
EDIT
Posting additional code for context. This is not the entirety of the code, there are some plugins that I'm not including code for and have excluded definitions for a several variables, but this should provide better insight into how things are working.
//Initialize the map
var map = new L.Map('map', {
maxZoom: mapMaxZoom,
minZoom: mapMinZoom,
crs: crs1848,
attributionControl: false,
layers: [pano1848]
});
//add controls to the map
var layerControl = L.control.layers(null, null, {position: 'bottomleft'}).addTo(map);
//building category layers from geojson
var types = ['African Americans', 'Art Architecture Culture', 'Education Religion Reform', 'Everyday Life', 'Immigrants', 'Science Inventions', 'Transportation Industry Commerce'];
types.forEach(function(type){
var catType = type.replace(/\s/g,"");
var catPoints = L.geoJson(mapData, {
filter: function(feature, layer){
var cat = feature.properties['category'];
return cat.indexOf(catType) >= 0;
},
onEachFeature: function (feature, layer) {
layer.bindTooltip(feature.properties.name);
(function(layer, properties){
//Create Numeric markers
var numericMarker = L.ExtraMarkers.icon({
icon: 'fa-number',
markerColor: 'yellow',
number: feature.properties['id']
});
layer.setIcon(numericMarker);
layer.on('click', function() {
$.ajax({
url:feature.properties['url'],
dataType:'html',
success: function(result){
$('#detailContainer').html(result);
$('#overlay').fadeIn(300);
}
});
});
})(layer, feature.properties);
}
});
layerControl.addOverlay(catPoints, catType);
});
//Base Layer Change Event
map.on('baselayerchange', function(base){
var layerName;
layerControl._layers.forEach(function(layer){
if(layer.overlay){
map.removeLayer(layer.layer)
}
});
if(base._url.indexOf('1848') >= 0){
map.options.crs = crs1848;
map.fitBounds([
crs1848.unproject(L.point(mapExtent1848[2], mapExtent1848[3])),
crs1848.unproject(L.point(mapExtent1848[0], mapExtent1848[1]))
]);
var southWest = map.unproject([0, 8192], map.getMaxZoom());
var northEast = map.unproject([90112, 0], map.getMaxZoom());
map.setMaxBounds(new L.LatLngBounds(southWest, northEast));
map.addLayer(allPoints);
layerName = '1848 Panorama';
}
else if(base._url.indexOf('2018') >= 0){
map.options.crs = crs2018;
map.fitBounds([
crs2018.unproject(L.point(mapExtent2018[2], mapExtent2018[3])),
crs2018.unproject(L.point(mapExtent2018[0], mapExtent2018[1]))
]);
var southWest = map.unproject([0, 8192], map.getMaxZoom());
var northEast = map.unproject([49152, 0], map.getMaxZoom());
map.setMaxBounds(new L.LatLngBounds(southWest, northEast));
layerName = '2018 Panorama'
}
miniMap.changeLayer(minimapLayers[layerName]);
//map.setView(map.getCenter(), map.getZoom());
});
You may create global variable call "overlays", and remove it like an example below.
Here is the similar example to illustrate your problem jsFiddle
var overlays = {
'Name 1': catPoints,
'Name 2': catType
};
L.control.layers(null, overlays).addTo(map);
// Whenever you want to remove all overlays:
for (var name in overlays) {
map.removeLayer(overlays[name]);
}

Grid line only on data point for scratter points

I need to draw x & y intercepts for all data points in a scratter chart. I went through major and minor grid lines. But it could not be my perfect solution.
Like the image below:
The sample image with x and y intercepts only on data points
You can use the render function of the chart to draw the horizontal and vertical lines on the chart surface. In the following demo, I name the x and y axes so that in the render function I can use the getAxis() method along with slot and range. See DOCS.
DEMO
var data = [[0.67, 5.4], [2.2, 2], [3.1, 3]];
$("#chart").kendoChart({
series: [{
type: "scatter",
data: data,
markers: {size: 16},
}],
yAxis: { name: "value", majorGridLines: {visible: false } },
xAxis: { name: "category", majorGridLines: {visible: false } },
render: function(e){
var chart = e.sender;
var yAxis = chart.getAxis("value");
var xAxis = chart.getAxis("category");
//iterate each point on the chart
for (var i=0; i<data.length; i++){
//vertical line
var valRange = yAxis.range();
var valSlot = yAxis.slot(valRange.min, valRange.max);
var point = data[i];
var catSlot = xAxis.slot(point[0]);
var path = new kendo.drawing.Path({
stroke: {color: "#B3BDBD", width: 1}
}).moveTo(catSlot.origin.x + catSlot.size.width/2, valSlot.origin.y)
.lineTo(catSlot.origin.x + catSlot.size.width/2, valSlot.bottomRight().y);
chart.surface.draw(path);
//horizontal line
var ySlot = yAxis.slot(point[1]);
var xRange = xAxis.range();
var xSlot = xAxis.slot(xRange.min, xRange.max);
var pathH = new kendo.drawing.Path({
stroke: {color: "#B3BDBD", width: 1}
}).moveTo(xSlot.origin.x, ySlot.origin.y + ySlot.size.width/2)
.lineTo(xSlot.bottomRight().x, ySlot.origin.y + ySlot.size.width/2);
chart.surface.draw(pathH);
}
}
});

leaflet snap polyline to rout

I'm try to convert polyline to rout but I have a problem
You see in the picture below that the road is different from the polyline:
Here is my code:
var mymap = L.map('map').setView([32.661343, 51.680374], 6);
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(mymap);
var markers = new L.MarkerClusterGroup();
var markerList = [];
var a = [];
var myTrip = [];
var myTrip2 = [];
for (var i = 1; i < locations.length ; i++) {
myTrip.push(new L.LatLng(parseFloat(locations[i]['Received']['lat']),parseFloat(locations[i]['Received']['lng'])));
a[0] = parseFloat(locations[i]['Received']['lat']);
a[1] = parseFloat(locations[i]['Received']['lng']);
var marker = new L.Marker(new L.LatLng(a[0], a[1]));
marker.bindPopup((locations[i]['Received']['id']).toString());
markerList.push(marker);
var polyline =L.polyline(myTrip, {color: 'blue'}).addTo(mymap);
}
var markerPatterns = L.polylineDecorator(myTrip, {
patterns: [
{offset: 25, repeat: 50, symbol: L.Symbol.arrowHead({pixelSize: 15, pathOptions: {fillOpacity: 1, weight: 0}})}
]
}).addTo(mymap);
var control = L.Routing.control({
waypoints: myTrip,
show: false,
waypointMode: 'snap',
showAlternatives: true,
useZoomParameter: true,
createMarker: function() {}
}).addTo(mymap);
1) So are the lines drawn using the raw coordinates? In other words, are the lines drawn like you do not use a routing service?
2) Leaflet Routing Machine is a plug-in that supports several routing engines, with OSRM as a default.
http://www.liedman.net/leaflet-routing-machine/
Have you tried inserting your DB coordinates to the OSRM-demo? Is it giving you the expected results?
http://map.project-osrm.org/
The high and low streets are routes because 1 of your waypoints are hitting that street and to get back on track again it have to turn around the block. It would have routed correcly if your waypoints where more accurate.

Markercluster toggle ON / OFF

They asked me for a 'toggle button' to switch the clustering on and off Can someone help me to achieve clustering on/off?
Note: loading more than 30,000 points
Create two layers, one with and one without the marker clustering and add them to the leaflet control. For example:
var littleton = L.marker([39.61, -105.02]).bindPopup('This is Littleton, CO.'),
denver = L.marker([39.74, -104.99]).bindPopup('This is Denver, CO.'),
aurora = L.marker([39.73, -104.8]).bindPopup('This is Aurora, CO.'),
golden = L.marker([39.77, -105.23]).bindPopup('This is Golden, CO.');
var cities = L.layerGroup([littleton, denver, aurora, golden]);
var citiesClustered = new L.MarkerClusterGroup();
markers.addLayer(littleton);
markers.addLayer(denver);
markers.addLayer(aurora);
markers.addLayer(golden);
var streets = L.tileLayer(mapboxUrl, {id: 'examples.map-i86knfo3', attribution: mapboxAttribution});
var map = L.map('map', {
center: [39.73, -104.99],
zoom: 10,
layers: [streets, cities]
});
var baseMaps = {
"Streets": streets
};
var overlayMaps = {
"Cities": cities,
"Clustered cities": citiesClustered
};
L.control.layers(baseMaps, overlayMaps).addTo(map);
You can also create a custom control that will de-cluster the markers but this control already exists and it's easy to implement.