21 Commits

Author SHA1 Message Date
Râu Cao a8c0aefbd6 1.8.0 2022-09-14 19:05:41 +02:00
Râu Cao 9c18cc19b7 Add popover for rider markers 2022-09-14 19:05:15 +02:00
Râu Cao 1dc218ca8a 1.7.0 2022-09-14 18:50:38 +02:00
Râu Cao bfd9b4fdf6 Add riders to map 2022-09-14 18:50:02 +02:00
Râu Cao 67707b7ded 1.6.0 2022-09-14 10:18:29 +02:00
Râu Cao 9c96037e32 Add legacy routes 2022-09-14 10:17:55 +02:00
Râu Cao fc4c63b519 1.5.0 2022-09-13 07:55:53 +02:00
Râu Cao fe1c9f6300 Adjust for multiple tracked POIs 2022-09-13 07:55:09 +02:00
Râu Cao 0d66bc1266 1.4.0 2022-09-11 12:31:26 +02:00
Râu Cao ba3982d9a6 Add plausible analytics script 2022-09-11 12:30:52 +02:00
Râu Cao f2cc9a9783 1.3.0 2022-09-09 20:29:41 +02:00
Râu Cao 0c1aeebdf7 Load current tour status from server 2022-09-09 20:29:18 +02:00
Râu Cao 3324a57206 Update route 2022-09-09 20:29:00 +02:00
Râu Cao cf8e6882f8 1.2.1 2022-09-08 09:49:18 +02:00
Râu Cao 6c4959deda Hackety hack, no time 2022-09-08 09:48:51 +02:00
Râu Cao e00b5576e6 1.2.0 2022-09-06 21:09:39 +02:00
Râu Cao e06b9d5109 MVP 2022-09-06 21:09:06 +02:00
Râu Cao b527049acb 1.1.0 2022-09-04 20:55:44 +02:00
Râu Cao 18ea53313f WIP tracked POIs 2022-09-04 20:53:37 +02:00
Râu Cao 9e839def7a 1.0.1 2022-09-03 11:54:42 +02:00
Râu Cao 47f1fce82d Use public dir for static assets 2022-09-03 11:54:15 +02:00
16 changed files with 229169 additions and 52774 deletions
+182553
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-11
View File
File diff suppressed because one or more lines are too long
+11
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

+3 -1
View File
@@ -6,11 +6,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Road2Bitcoin Live Map</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css">
<script type="module" crossorigin src="/assets/index.7920774d.js"></script>
<script defer data-domain="r2b22.kip.pe" src="https://plausible.io/js/plausible.js"></script>
<script type="module" crossorigin src="/assets/index.8fd3a327.js"></script>
<link rel="stylesheet" href="/assets/index.eed9f443.css">
</head>
<body>
<div id="map"><div id="popup"></div></div>
<div id="people"></div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
+2
View File
@@ -6,9 +6,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Road2Bitcoin Live Map</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css">
<script defer data-domain="r2b22.kip.pe" src="https://plausible.io/js/plausible.js"></script>
</head>
<body>
<div id="map"><div id="popup"></div></div>
<div id="people"></div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js"></script>
<script type="module" src="./main.js"></script>
</body>
+269 -163
View File
@@ -8,175 +8,281 @@ import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer';
import {Circle as CircleStyle, Fill, Icon, Stroke, Style} from 'ol/style';
import {OSM, Vector as VectorSource} from 'ol/source';
import {useGeographic} from 'ol/proj';
import geojsonRoute from './geo/route.json'
import geojsonPOI from './geo/poi.json'
import geojsonRoute from './data/r2b22-route.json'
import geojsonPOI from './data/r2b22-poi.json';
import geojsonLegacy from './data/legacy-route.json';
useGeographic();
const styles = {
lineOrange: new Style({
stroke: new Stroke({
color: '#FF9900',
// lineDash: [8],
width: 5,
}),
}),
lineGrey: new Style({
stroke: new Stroke({
color: '#555555',
// lineDash: [8],
width: 5,
}),
}),
iconStop: new Style({
image: new Icon({
anchor: [0.5, 46],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
src: 'data/icon.png',
}),
}),
iconVan: new Style({
image: new Icon({
anchor: [0.5, 16],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
src: 'data/van-100px.png',
}),
}),
circleBlack: new Style({
image: new CircleStyle({
radius: 7,
fill: new Fill({color: '#FF9900'}),
async function main() {
const styles = {
lineOrange: new Style({
stroke: new Stroke({
color: 'white',
width: 2,
color: '#FF9900',
// lineDash: [8],
width: 5,
}),
})
})
}
//
// Route
//
const lastStageFinished = 4;
const stagesCompleted = geojsonRoute.features.slice(0, lastStageFinished);
const stagesAhead = geojsonRoute.features.slice(lastStageFinished);
const vectorSourceStagesCompleted = new VectorSource();
const vectorSourceStagesAhead = new VectorSource();
for (const stage of stagesCompleted) {
vectorSourceStagesCompleted.addFeature(new GeoJSON().readFeature(stage));
}
for (const stage of stagesAhead) {
vectorSourceStagesAhead.addFeature(new GeoJSON().readFeature(stage));
}
const stagesCompletedLayer = new VectorLayer({
source: vectorSourceStagesCompleted,
style: styles.lineOrange
});
const stagesAheadLayer = new VectorLayer({
source: vectorSourceStagesAhead,
style: styles.lineGrey
});
//
// Points of Interest
//
const vectorSourcePOI = new VectorSource({
features: new GeoJSON().readFeatures(geojsonPOI),
});
const poiLayer = new VectorLayer({
source: vectorSourcePOI,
style: styles.circleBlack,
});
const vectorSourceVan = new VectorSource();
const vanFeature= new Feature({
geometry: new Point([ 12.498556, 45.780383 ]),
name: 'Support Van'
});
vectorSourceVan.addFeature(vanFeature);
const vanLayer = new VectorLayer({
source: vectorSourceVan,
style: styles.iconVan
});
//
// Map initialization
//
const view = new View({
center: [10.6, 46.9],
zoom: 6.6
})
const map = new Map({
target: 'map',
layers: [
new TileLayer({
source: new OSM()
}),
stagesCompletedLayer,
stagesAheadLayer,
poiLayer,
vanLayer
],
view: view
});
//
// Popups
//
const popupEl = document.getElementById('popup');
const popup = new Overlay({
element: popupEl,
positioning: 'bottom-center',
stopEvent: false,
});
map.addOverlay(popup);
let popover;
function disposePopover() {
if (popover) {
popover.dispose();
popover = undefined;
lineGrey: new Style({
stroke: new Stroke({
color: '#555555',
// lineDash: [8],
width: 5,
}),
}),
iconStop: new Style({
image: new Icon({
anchor: [0.5, 46],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
src: '/img/icon.png',
}),
}),
iconVan: new Style({
image: new Icon({
anchor: [0.5, 16],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
src: '/img/van-100px.png',
}),
}),
circleBlack: new Style({
image: new CircleStyle({
radius: 7,
fill: new Fill({color: '#FF9900'}),
stroke: new Stroke({
color: 'white',
width: 2,
}),
})
})
}
//
// Route
//
const tourStatus = await fetch('https://r2b22.kip.pe/status.json').then(res => res.json());
const lastStageFinished = tourStatus.lastStageFinished;
const stagesCompleted = geojsonRoute.features.slice(0, lastStageFinished);
const stagesAhead = geojsonRoute.features.slice(lastStageFinished);
const vectorSourceStagesCompleted = new VectorSource();
const vectorSourceStagesAhead = new VectorSource();
for (const stage of stagesCompleted) {
vectorSourceStagesCompleted.addFeature(new GeoJSON().readFeature(stage));
}
for (const stage of stagesAhead) {
vectorSourceStagesAhead.addFeature(new GeoJSON().readFeature(stage));
}
const stagesCompletedLayer = new VectorLayer({
source: vectorSourceStagesCompleted,
style: styles.lineOrange
});
const stagesAheadLayer = new VectorLayer({
source: vectorSourceStagesAhead,
style: styles.lineGrey
});
//
// Points of Interest
//
const vectorSourcePOI = new VectorSource({
features: new GeoJSON().readFeatures(geojsonPOI),
});
const poiLayer = new VectorLayer({
source: vectorSourcePOI,
style: styles.circleBlack,
});
const vectorSourceTrackedPoints = new VectorSource();
const vanFeature= new Feature({
geometry: new Point([8.918618, 44.407408]),
name: 'Support Van'
});
vectorSourceTrackedPoints.addFeature(vanFeature);
const trackedPointsLayer = new VectorLayer({
source: vectorSourceTrackedPoints,
style: styles.iconVan
});
//
// Legacy routes
//
const vectorSourceLegacy = new VectorSource();
vectorSourceLegacy.addFeatures(new GeoJSON().readFeatures(geojsonLegacy));
const legacyLayer = new VectorLayer({
source: vectorSourceLegacy,
style: styles.lineOrange
});
//
// Map initialization
//
const view = new View({
center: [10.6, 46.9],
zoom: 6.6
})
window.view = view;
const map = new Map({
target: 'map',
layers: [
new TileLayer({
source: new OSM()
}),
stagesCompletedLayer,
stagesAheadLayer,
legacyLayer,
poiLayer,
trackedPointsLayer
],
view: view
});
//
// Center map on current/next stage
//
setTimeout(() => {
const nextStageFeature = new GeoJSON().readFeature(stagesAhead[0]);
view.fit(nextStageFeature.getGeometry(), {
maxZoom: 10,
duration: 1000
});
}, 3000);
//
// Popups
//
const popupEl = document.getElementById('popup');
const popup = new Overlay({
element: popupEl,
positioning: 'bottom-center',
stopEvent: false,
});
map.addOverlay(popup);
let popover;
function disposePopover() {
if (popover) {
popover.dispose();
popover = undefined;
}
}
function createPopoverHtml(feature) {
const container = document.createElement('div');
const title = document.createElement('div');
title.textContent = feature.get('name');
container.append(title);
return container.innerHTML;
}
// display popup on click
map.on('click', function (evt) {
const feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) {
return feature;
});
disposePopover();
if (!feature) return;
popup.setPosition(evt.coordinate);
popover = new bootstrap.Popover(popupEl, {
placement: 'top',
html: true,
content: createPopoverHtml(feature)
});
popover.show();
});
// change mouse cursor when over marker
map.on('pointermove', function (evt) {
map.getTargetElement().style.cursor = map.hasFeatureAtPixel(evt.pixel)
? 'pointer'
: '';
});
// Close the popup when the map is moved
map.on('movestart', disposePopover);
//
// Tracking
//
const updateInterval = 10000;
const peopleOverlays = {};
function createParticipantHTML (name) {
if (document.getElementById(`user-${name}`)) return;
const el = document.createElement('img');
el.src = `https://r2b22.kip.pe/avatars/${name}.png`;
el.id = `user-${name}`;
el.style = 'width: 40px; height: 40px; border-radius: 20px; cursor: pointer';
document.getElementById('people').append(el);
}
function createParticipantOverlay (name) {
if (peopleOverlays[name]) return;
const overlayElement = new Overlay({
stopEvent: false,
positioning: 'center-center',
element: document.getElementById(`user-${name}`)
});
peopleOverlays[name] = overlayElement;
map.addOverlay(overlayElement);
}
function updateData(startInterval=false) {
fetch('https://r2b22.kip.pe/last.json')
.then(response => response.json())
.then(data => {
const vanData = data.find(i => i.name == 'satoshithevan');
const vanCoords = [vanData.lon, vanData.lat];
vanFeature.getGeometry().setCoordinates(vanCoords);
for (const item of data) {
if (!tourStatus.participants.includes(item.name)) continue;
createParticipantHTML(item.name);
createParticipantOverlay(item.name);
const overlay = peopleOverlays[item.name];
overlay.setPosition([item.lon, item.lat]);
function clickHandler () {
disposePopover();
popup.setPosition([item.lon, item.lat]);
popover = new bootstrap.Popover(popupEl, {
placement: 'top',
html: true,
content: `Rider: ${item.name}`
});
popover.show();
}
const avatarEl = document.getElementById(`user-${item.name}`);
avatarEl.removeEventListener('click', clickHandler);
avatarEl.addEventListener('click', clickHandler);
}
});
if (startInterval) {
setInterval(updateData, updateInterval);
}
}
updateData(true);
}
// display popup on click
map.on('click', function (evt) {
const feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) {
return feature;
});
disposePopover();
if (!feature) {
return;
}
popup.setPosition(evt.coordinate);
popover = new bootstrap.Popover(popupEl, {
placement: 'top',
html: true,
content: feature.get('name'),
});
popover.show();
});
// change mouse cursor when over marker
map.on('pointermove', function (evt) {
map.getTargetElement().style.cursor = map.hasFeatureAtPixel(evt.pixel)
? 'pointer'
: '';
});
// Close the popup when the map is moved
map.on('movestart', disposePopover);
main();
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "ol-vite",
"version": "1.0.0",
"version": "1.8.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "ol-vite",
"version": "1.0.0",
"version": "1.8.0",
"dependencies": {
"ol": "latest"
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "map",
"version": "1.0.0",
"version": "1.8.0",
"scripts": {
"start": "vite",
"build": "vite build",
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

+1
View File
@@ -4,6 +4,7 @@ html, body {
margin: 0;
height: 100%;
}
#map {
position: absolute;
top: 0;