There are plenty of lenghty explanations around the web on how to clone a Linux OS onto a new device, but I came through this great tool on my Pi which reduces it to a few minutes and no typing at all.
It is a built-in “SD Card Copier” which you can use to regularly backup your OS, or upgrade your file system to a larger SD card, which is my case.
Content
1. Setup.
As mentioned, I want to move my Raspbian OS to a larger and faster SD card. Both cards need to be accessible on your Pi, so I got it set up like this:
Raspberry Pi 4B with Raspbian 10 Buster
32Gb SD card on Pi's SD card slot.
128Gb fast SD card on USB adapter via Pi's USB
For a backup, you can plug in a USB stick and transfer the backup onto it.
We will be using the "SD Card Copier" or "piclone", which should be built-in but you can install it on Raspbian via:
sudo apt-get update
sudo apt-get install piclone
[Not tested!] Piclone should work for other Linux distributions, if you're skillful enough to build all the required dependencies. In that case, you can access the github repository and follow the instructions there:
Did you wonder where did I get the header picture?
It’s not from the internet, not from any telescope. It’s from the great software Stellarium, an open-source planetarium with astronomical precision and unbeatable realism.
Content
Requirements
Stellarium is available for Windows, Mac and Linux, with a web version seamingly amazing, but lacking the configuration and plugins options that are offered by the desktop versions.
As they indicate in their website, the minimum requirements are:
Linux/Unix; Windows 7 and above; Mac OS X 10.12.0 and above
3D graphics card which supports OpenGL 3.0 and GLSL 1.3 or OpenGL ES 2.0
512 MiB RAM
600 MiB on disk
Keyboard
Mouse, Touchpad or similar pointing device
And recommended:
64-bit operating system
Linux/Unix; Windows 7 and above; Mac OS X 10.12.0 and above
3D graphics card which supports OpenGL 3.3 and above
1 GiB RAM or more
1.5 GiB on disk
Keyboard
Mouse, Touchpad or similar pointing device
Moderately dark environment (deep shadow or indoors)
Basically it works in almost any device. Just download it from one of the options in the top menu.
Initial configuration
At startup, the position of all objects is synchronized with the PC time and it will be updated in real-time.
Using the bottom menu (or its [shortcuts] ) we can activate/disable different view elements. I recommend that you play with all of them and set it as you like:
Constellation lines [C].
Constellation labels [V].
Constellation art [R].
Equatorial grid [E].
Azimuthal grid [Z].
Ground [G].
Atmosphere [A].
Cardinal points [Q].
Deep-sky objects [D].
Planet labels [Alt+P].
Switch between equatorial or azimuthal mount [Ctrl+M].
Center on selected object [Space].
Night mode [Ctrl+N].
Full-screen mode [F11].
Show exoplanets [Ctrl+Alt+E].
Toggle meteor showers [Ctrl+Shift+M].
Show meteor showers search dialog [Ctrl+Alt+M].
Satellite hints [Ctrl+Z].
Decrease time speed [J].
Set normal time rate [N].
Set time to now [8].
Increase time speed [L].
Quit [Ctrl+Q].
On the lateral menu we have the following options:
Location window [F6]. Here we can specify the location of our view, being a place on Earth or any other planet in the list.
Date/time window [F5]. We can choose any date from past or future as the orbits are perfectly known for centuries.
Sky and viewing options window [F4]. It lets us calibrate the visibility of objects, the projection or the landscape picture, between others.
Search window [F3]. It will locate an object from space by its name.
Configuration window [F2]. Shows settings like language, system time, the use of extras or scripts, etc.
Astronomical calculations window [F10]. Shows the details of calculations done by the software, like object positions, or events that occur today.
Help window [F1].
Uses
We can simply disable the atmosphere [A] and investigate the objects in space left-clicking on them, which will show information about the selected object. If we press [Space] the view will follow the object.
We can also activate constellation lines [C] and labels [V], show meteor showers [Ctrl+Shift+M] and advance time until the sun sets [F5] or [L]. This way we can find out if there will be a starry night (or any other night) and know its location.
Now in August we are reaching the Perseidas maximum activity, that will occur on 13th August according to the calculations for the Iberian peninsula, and will be located moving from North to Northeast.
Or simply play with the many possibilities of the software and get an incredible background picture like the one used on the header of this blog.
Do you imagine any other possibilities for the software? Any configuration tip or any worth script? Leave your comment on Twitter 🐦 See you soon!
UPDATE 2023! Hourly WMS service removed and daily version updated to V2. WMS version updated to 1.3.0
Recently it’s been trendy to talk about the increased warming of the Sea, and I’ve found very few maps where you can actually click and check the temperature it pretends to indicate.
So I’ve done my own:
I’m going to find a WMS with the required raster information,
Replicate the legend using html <canvas> elements,
And obtain the temperature value matching the pixel color value.
Content
The data
There are several and confusing information sources:
Puertos del Estado: My first attempt was to check the Spanish State Ports website, but the only good thing I found was that they use Leaflet.. They have a decent real-time viewer, but it's really complicated to access the data to do something with them (although I'll show you how in the future).
Copernicus: "Copernicus is the European Union's Earth observation programme", as they indicate in their website, and they gather the majority of geographical and environmental information produced in the EU member countries. In their marine section we'll find Sea Surface Temperature (SST) data obtained from satellite imagery and other instruments from different euopean organisms.
Global Ocean OSTIA Diurnal Skin Sea Surface Temperature
After trying them all, the most complete is the global map produced by the MetOffice.
As they indicate, it shows the hourly average in a gap-free map that uses in-situ, satellite and infrared radiometry.
The basemap
I'll create a basemap as I've shown in other posts.
I'll download it in geoJSON format and convert it into a variable called "countries" in a new file CNTR_RG_01M_2020_4326.js . I need to insert the following heading so it's read correctly (cole the JSON variable with "]}" so it's read correctly).
We'll add the GeoServer functions "?service=WMS&request=GetCapabilities" which will show the available information in the WMS like layers, legends, styles or dimensions.
Find the options values that you need in the metadata file. Specifically these tags:
<Layer queryable="1"><Name> will show the layer names that we can introduce in the proeprty "layers:"
<Style><Name> will show the name for the different styles available to represent the raster map and we'll introduce it in the property "styles:".
<LegenURL> shows a link to the legend used for that style.
<Dimension> will show the units that we can use when querying the WMS. In this case it's a time unit as we can vary the date that the map represents. I'll comment it for the moment so it will show the last available date.
SST raster data
Finally, let's add the legend to the map to have a visual reference.
It's inserted as an image in the <body> and some CSS in <style>:
As you can see, it's hard to try and figure out a value for any of the pixels in the map.
Mimic the legend to query the data
To know the temperature that a pixel represents, we should know the position of such pixel color in the legend, and its proportional value compared to the extreme values (310kPa= 36.85ºC y 210kPa=-3.15ºC).
The problem is that CORS policy won't let you query the image as it's outside our domain. On the other hand, if we add the image to our domain, it will have a low resolution and lose precision in the colors that it shows.
That's why I'll mimic the legend using a <canvas> element.
Using javascript, and the HTML Color Picker, we'll define the different color stops in a "gradient" type fill that will replace the original legend.
function grad(){
//Generamos la leyenda en el canvas 'gradientC'
var ctx=document.getElementById("gradientC").getContext('2d');
//Definimos un gradiente lineal
var grd=ctx.createLinearGradient(0,150,0,0);
//Calibrar las paradas del gradiente tomando muestras de la imagen
//Descomentar la imagen que va junto al canvas en el html
//Usar HTML color picker: https://www.w3schools.com/colors/colors_picker.asp
grd.addColorStop(0, "rgb(0, 0, 146)"); //0 -> -3.15
grd.addColorStop(0.09, "rgb(0, 0, 247)"); //1
grd.addColorStop(0.185, "rgb(1, 61, 255)"); //2
grd.addColorStop(0.26, "rgb(0, 146, 254)"); //3
grd.addColorStop(0.3075, "rgb(0, 183, 255)"); //4
grd.addColorStop(0.375, "rgb(3, 251, 252)"); //5
grd.addColorStop(0.5, "rgb(111, 255, 144)"); //6 -> 20.0
grd.addColorStop(0.575, "rgb(191, 255, 62)"); //7
grd.addColorStop(0.64, "rgb(255, 255, 30)"); //8
grd.addColorStop(0.74, "rgb(255, 162, 1)"); //9
grd.addColorStop(0.805, "rgb(255, 83, 0)"); //10
grd.addColorStop(0.90, "rgb(252, 4, 1)"); //11
grd.addColorStop(1, "rgb(144, 0, 0)"); //12 -> 36.85
//añadir el gradiente al canvas
ctx.fillStyle = grd;
ctx.fillRect(0,0,255,255);
}
//ejecutamos la funcion del gradiente al inicio
grad();
SST raster data
Pixel selector creation and temperature calculator
Once we have our own legend, we create another element that shows the selected pixel as well as the temperature obtained.
To use the pixel color, we'll add another <canvas>:
<canvas id="temp">
<img id="pixel" src="" ALT="CLICK PARA OBTENER TEMPERATURA"></img>
</canvas>
<div id="tempTxt">PINCHA SOBRE EL MAR PARA CONOCER SU TEMPERATURA</div>
onMapClick() to obtain the selected pixel and pass it to the lower box. We use a GET request of the WMS service using the coordinates of the pixel location. It's important to take into account the reference system which is not the usual (EPSG:3857) for unit conversions.
//Añadimos función al hacer click en el mapa
map.addEventListener('click', onMapClick);
function onMapClick(e) {
//ejecutamos la función grad con cada click
grad();
//Obtenemos las coordenadas del pinto seleccionado
var latlngStr = '(' + e.latlng.lat.toFixed(3) + ', ' + e.latlng.lng.toFixed(3) + ')';
//console.log(latlngStr);
//Definir el CRS para enviar la consulta al WMS
const proj = L.CRS.EPSG3857;
//const proj = L.CRS.EPSG4326;
//Definimos los límites del mapa que pediremos al WMS para que sea aproximadamente de 1 pixel
var BBOX=((proj.project(e.latlng).x)-10)+","+((proj.project(e.latlng).y)-10)+","+((proj.project(e.latlng).x)+10)+","+((proj.project(e.latlng).y)+10);
//console.log(BBOX);
//Restablecemos la imagen en cada click
var tTxt=document.getElementById("tempTxt");
var pix= document.getElementById("pixel");
var ctx=document.getElementById("temp").getContext("2d");
//pix.src="";
ctx.fillStyle="lightgrey";
ctx.fillRect(0,0,300,300);
//Realizamos la petición del pixel seleccionado
var xPix= new XMLHttpRequest();
xPix.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200) {
pix.src=URL+WMS+BBOX;
pix.onload=function(){
ctx.drawImage(pix,0,0,300,300);
tTxt.innerHTML="INTERPRETANDO LEYENDA...";
//Interpretamos el pixel según la leyenda
leyenda();
}
pix.crossOrigin="anonymous";
}
};
xPix.open("GET", URL+WMS+BBOX);
xPix.send();
tTxt.innerHTML="CARGANDO TEMPERATURA...";
}
leyenda() calculates the temperature of the selected pixel according to our legend. It shows the value in the lower box and also adds a white indication mark in the legend. The calculation algorithm consists in running the legend pixel by pixel (vertically) and comparing the difference of the rgb(x,y,z) values of the legend with the rgb values in the selected pixel. We'll be keeping the minimum difference until we reach the last pixel, so there will be situations where the solution is not 100% exact. This is not the best way, but it's fast (to understand and execute) and quite effective.
function leyenda(){
var ctx=document.getElementById("temp").getContext("2d");
var tTxt=document.getElementById("tempTxt");
//obtenemos el valor RGB del pixel seleccionado
var RGB=ctx.getImageData(5,5,1,-1).data;
//console.log(ctx.getImageData(10,10,1,-1).data);
var key=document.getElementById("gradientC").getContext("2d");
var max=150;
var min=1000;//la máxima diferencia sólo puede ser de 255x3=765
var dif="";
var val="";
//recorremos el gradiente de la leyenda pixel a pixel para obtener el valor de temperatura
for(var p=1;p<=max;p++){
//obtenemos el valor actual
var temp=key.getImageData(1,p,1,-1).data;
//console.log(temp);
//comparamos con el seleccionado y obtenemos la diferencia total
dif=Math.abs(parseInt(temp[0])-parseInt(RGB[0]))+Math.abs(parseInt(temp[1])-parseInt(RGB[1]))+Math.abs(parseInt(temp[2])-parseInt(RGB[2]));
if(dif<min){
min=dif;
val=p;
//console.log("Obj:"+RGB[0]+","+RGB[1]+","+RGB[2]+"\nTemp:"+temp[0]+","+temp[1]+","+temp[2]+"\nDif:"+dif);
}
}
var T=36.85-(val*40/max);
T=T.toFixed(2);
//pintamos una línea de referencia en la leyenda
key.fillStyle="white";
key.fillRect(0,val,255,1);
//definimos la temperatura en el texto
//si el color da gris, hemos pinchado en la tierra
//console.log("T= "+T);
if(RGB[0]==211&RGB[1]==211&RGB[2]==211){
tTxt.innerHTML="PINCHA SOBRE EL MAR PARA CONOCER SU TEMPERATURA";
}else if(typeof T == "undefined"){
tTxt.innerHTML="¡ERROR!<BR>PRUEBA OTRO PUNTO DEL MAR.";
}else{
tTxt.innerHTML="TEMPERATURA APROXIMADA: <br><br>"+T+" ºC";
}
//console.log(key.getImageData(1,150,1,-1).data);
}
We also ommit the original legend and add some custom text.
//Obtener última hora de actualizacion del mapa
var timeInp=document.getElementById("timeInp");
var t;
var maxT;
var minT;
var xTime = new XMLHttpRequest();
xTime.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200) {
//convertimos el XML según https://www.w3schools.com/xml/xml_parser.asp
var xmlDoc=this.responseXML;
t=xmlDoc.children[0].children[1].children[2].children[12].children[1].children[5].attributes[4].value;
//Lo convertimos en un objeto fecha quitando los segundos
t=new Date(t);
t=t.toISOString().substring(0,t.toISOString().length-8);
//Lo pasamos al selector y lo establecemos como máximo
timeInp.max=t;
timeInp.value=t;
maxT=new Date(t);
//también establecemos el minimo
t=xmlDoc.children[0].children[1].children[2].children[12].children[1].children[5].innerHTML.trim();
t=t.substring(0,16);
timeInp.min=t;
minT=new Date(t);
}
};
xTime.open("GET", URL+"?service=WMS&request=GetCapabilities");
xTime.send();
//Selector de fecha para WMS
timeInp.addEventListener("change", function(){
t=new Date(timeInp.value.toString());
t.setHours(12-t.getTimezoneOffset()/60);
t=t.toISOString().substring(0,t.toISOString().length-8);
timeInp.value=t;
t=new Date(timeInp.value);
t.setHours(12-t.getTimezoneOffset()/60);
//si estamos en el rango de datos..
if(t>=minT && t<=maxT){
t=t.toISOString();
//actualziamos el mapa
base.setParams({
time: t,
styles: "boxfill/rainbow",
},0);
}else{//mostramos error
alert("La fecha introducida está fuera de rango.");
}
});
//funcion de reinicio de fecha
function reiniT(){
timeInp.value=maxT.toISOString().substring(0,maxT.toISOString().length-13)+"23:30";
}
Result
Joining it all, you'll end up with something like this, which you can see fullscreen here:
If you liked this, have any doubts or any idea to improve this or other maps, drop your thought in Twitter 🐦
I also add <meta> charset(1) and viewport(2) properties to (1)correctly read special characters in the data and (2) adjust the window correctly for mobile devices.
As you can see, I add border:0 and margin:0 to <body> style. This adjusts the map to fit the window without white spaces.
Height:100vh and width:100vw adjust the map to fit the size of the screen, even when we resize (view height and view width). z-index will be used to place elements in order and avoid overlays.
Now we can generate the map with javascript in the <script> block.
With L.map we define the map and the initial view (centered in Madrid).
With L.tileLayer.wms we add the WMS service to get an "onlin" basemap. You can find the URLs for each service in the metadata files that come with each product.
It's also usual that WMS come with several layers and we need to define one. We can get a list of layers using the GetCapabilities WMS function. In my case, its the <Layer> with <Name> "IGNBaseTodo-gris" found here:
Finally, to see the app in one click on a smartphone, you need to copy the content in leafMET.js and add it inside the <script> tags in the .html
This way, you'll have a single file with all it's needed to run the webapp. I added <title> and "icon" tags in the <head> to show a page title and an icon in the shortcut.
Blocking page when accessing Spotify via Firefox ESR or Chromium
You would have thought that you could just plug your Raspberry Pi to your monitor and play some background music on Spotify through your browser from the usual open.spotify.com. Well, you probably found the message above and ran in circles for a while until you got here. Glad I can help!
Increased security on Spotify makes it only available via 64bit versions of Chrome, Firefox and Edge which restrict hacking and fraudulent streaming without a premium account.
Content
Premium.
If you have a premium account, go the easy way and make use of one of the apps available like Raspicast (to stream from your phone), Raspotify and spotify-qt, which will grant you access to your music with some little config.
spotify-qt from snapcraft.io
Non-Premium.
For those of us who love listening to ads (and not paying a buck), the journey is not that hard. We just need to install the Widevine library:
sudo apt-get install libwidevinecdm0
Then we reboot the system and access open.spotify.com via Chromium.
Log in and you should see the usual interface without the previous annoying message.
Lastly, we can install a shortcut in our desktop using the button in the navigation bar. And that´s it! Now you can fully enjoy Spotify on your Raspberry!
Spotify web in Chromium Browser for Raspberry Pi
Any issues or comments, let me know via Twitter 🐦! See you soon!
Leaflet JS is one of the most exciting discoveries I’ve come across in the latest years. It is one of the most powerful mapping libraries you’ll find around, competing with Google’s API, ESRI and Mapbox thanks to its great community and the awesome plugins that have been developed for it.
Open-source FTW 🤟
(See the code for above map in this post with a few more Leaflet samples)
Content
1. Set up.
All you need to use Leaflet JS is the following:
A web browser with html and javascript compatibility (any modern browser like Firefox, Opera, Chrome, Edge, Vivaldi, etc will do).
Any text editor.
[OPTIONAL] A web server to host your resulting webpage and make it accessible via internet. In any case, you can always run the file locally except for some special features.
2. Integrating Leaflet JS in any webpage.
Being a library, you only need to link its scripts to make use of it.
Start by creating a new file with a basic html structure and extension .html. I always use and recommend w3schools for any help:
This method will read the library from the internet, but you can download the library files from Leaflet's website to your PC and make proper reference to the local files in your html code:https://leafletjs.com/download.html
This options will make use of a local copy of the library and won't be affected by changes to the live repository. It also allows you to adapt the code locally to suit your application.
3. Adding a map to html <body> with Leaflet JS
Location inside html <body>
Leaflet will add its components inside a <div> element which you can place anywhere inside your html <body> tags to suit your design. You need to specify a div "id" in order to refer to it later. I am using "mapid" for this example:
<div id="mapid" style="height:500px;"></div>
The map will be affected by CSS properties assigned to the div tag (#mapid in my case). This will modify how the div element fits and looks in your website.
As an example, I'll modify the frame to be round with a 30px radius, instead of the default square edge. We'll add this code as CSS inside <style> tags. Uncomment these lines to see the difference.
<style>
#mapid{border-radius: 30px;}
</style>
Define the map in javascript
Let's now add the map object itself. This is done using the library functions (which start with L). We'll add all this function inside the <script> tags as they make use of javascript.
Define the map object. Make use of the function "L.map" and refer to the "mapid" div element. Then define the start view coordinates and zoom level with "L.setView".
I am going to set the center of the view approximately in Miami (make a google search and extract its latitude and logitude coordinates: 25.75, -80.2. The zoom range will be set to 12 so it's close enough. Larger values mean bigger zoom in and smaller values provide a wider view.
var map = L.map('mapid').setView([25.75, -80.2], 12);
Define the base layer. Web-based maps make use of tiled images which are loaded dinamically according to your navigation across the map. We can use the "L.tileLayer" function and any of the following tiled services very well put together in this post, which includes Open Street Maps (OSM), Google Maps, and ESRI services. Being will be using the OSM basemap for this example:
Finally, let's add a marker, a basic functionality you will want in most maps. We can set the text inside "bindPopup" being able to format with html tags. "openPopup" function displays the pop up directly at start.
//marker object
var marker = L.marker([25.77, -80.13]).addTo(map);
//pop up object attached to marker object
marker.bindPopup("<b>I'm in</b><br>Miami Beach!").openPopup();
Final result.
The rectangle below is a HTML block with all the code above put together. It should which display a OSM basemap centered in Miami with a marker in Miami Beach.
The source code inside the HTML block above is exactly the following:
TIP! You can copy paste this code in a text file with .html extension and preview locally in a browser.
Don't miss the Docs and Plugins sections in the Leaflet JS website. There are plenty of features that you can add to your map. The most interesting are: