Boat tracker
As you can see, all stuff is handled by a well documented Leaflet API. Create your own backend, serving GeoJSON tracks with boats/planes/cars and use the code below if you would like.
As you can see, all stuff is handled by a well documented Leaflet API. Create your own backend, serving GeoJSON tracks with boats/planes/cars and use the code below if you would like.
Download complete example from GitHub
<html>
<head>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, shrink-to-fit=no"
/>
<script src="https://unpkg.com/leaflet@1.4.0/dist/leaflet.js"></script>
<script src="https://api.windy.com/assets/map-forecast/libBoot.js"></script>
<style>
#windy {
width: 100%;
height: 300px;
}
</style>
</head>
<body>
<div id="windy"></div>
<script src="script.js"></script>
</body>
</html>
const options = {
key: 'PsLAtXpsPTZexBwUkO7Mx5I', // REPLACE WITH YOUR KEY !!!
lat: 14,
lon: -29,
zoom: 4,
};
windyInit(options, windyAPI => {
const { map } = windyAPI;
const MARKER = encodeURIComponent(`<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<path d="M4.784,13.635c0,0 -0.106,-2.924 0.006,-4.379c0.115,-1.502 0.318,-3.151 0.686,-4.632c0.163,-0.654 0.45,-1.623 0.755,-2.44c0.202,-0.54 0.407,-1.021 0.554,-1.352c0.038,-0.085 0.122,-0.139 0.215,-0.139c0.092,0 0.176,0.054 0.214,0.139c0.151,0.342 0.361,0.835 0.555,1.352c0.305,0.817 0.592,1.786 0.755,2.44c0.368,1.481 0.571,3.13 0.686,4.632c0.112,1.455 0.006,4.379 0.006,4.379l-4.432,0Z" style="fill:rgb(0,46,252);"/><path d="M5.481,12.731c0,0 -0.073,-3.048 0.003,-4.22c0.06,-0.909 0.886,-3.522 1.293,-4.764c0.03,-0.098 0.121,-0.165 0.223,-0.165c0.103,0 0.193,0.067 0.224,0.164c0.406,1.243 1.232,3.856 1.292,4.765c0.076,1.172 0.003,4.22 0.003,4.22l-3.038,0Z" style="fill:rgb(255,255,255);fill-opacity:0.846008;"/>
</svg>`);
const MARKER_ICON_URL = `data:image/svg+xml;utf8,${MARKER}`;
const BoatIcon = L.icon({
iconUrl: MARKER_ICON_URL,
iconSize: [24, 24],
iconAnchor: [12, 12],
popupAnchor: [0, 0],
});
const markers = [];
const updateIconStyle = () => {
for (const marker of markers) {
const icon = marker._icon;
if (!icon) {
continue;
}
const heading = icon.getAttribute('data-heading');
if (icon.style.transform.indexOf('rotateZ') === -1) {
icon.style.transform = `${
icon.style.transform
} rotateZ(${heading || 0}deg)`;
icon.style.transformOrigin = 'center';
}
}
};
fetch('boats.json')
.then(response => response.json())
.then(result => result.result)
.then(result => {
try {
let hue = 0;
for (const boatName of Object.keys(result)) {
hue = (hue + 60) % 360;
const boat = result[boatName];
const layer = L.polyline(boat.track, {
color: `hsl(${hue}, 100%, 45%)`,
weight: 2,
}).addTo(map);
layer.on('mouseover', function() {
layer.setStyle({
weight: 4,
});
});
layer.on('mouseout', function() {
layer.setStyle({
weight: 2,
});
});
const marker = L.marker(boat.track[boat.track.length - 1], {
icon: BoatIcon,
}).addTo(map);
markers.push(marker);
marker._icon.setAttribute('data-heading', boat.heading);
marker.bindPopup(boatName);
updateIconStyle();
}
} catch (error) {
console.error(`Error querying boats: ${error.message}`);
}
})
.catch(error => {
console.error(`Error querying boats: ${error.message}`);
});
// Handle some events. We need to update the rotation of icons ideally each time
// leaflet re-renders. them.
map.on('zoom', updateIconStyle);
map.on('zoomend', updateIconStyle);
map.on('viewreset', updateIconStyle);
});