Compare commits
3 Commits
cf8e6882f8
...
f2cc9a9783
Author | SHA1 | Date |
---|---|---|
Râu Cao | f2cc9a9783 | |
Râu Cao | 0c1aeebdf7 | |
Râu Cao | 3324a57206 |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -6,7 +6,7 @@
|
|||
<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.03aa5401.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index.148d1b99.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index.eed9f443.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
98921
geo/route.json
98921
geo/route.json
File diff suppressed because it is too large
Load Diff
468
main.js
468
main.js
|
@ -13,257 +13,263 @@ import geojsonPOI from './geo/poi.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: '/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'}),
|
||||
async function main() {
|
||||
const styles = {
|
||||
lineOrange: new Style({
|
||||
stroke: new Stroke({
|
||||
color: 'white',
|
||||
width: 2,
|
||||
color: '#FF9900',
|
||||
// lineDash: [8],
|
||||
width: 5,
|
||||
}),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// Route
|
||||
//
|
||||
|
||||
const lastStageFinished = 2;
|
||||
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',
|
||||
trackable: true
|
||||
});
|
||||
|
||||
vectorSourceTrackedPoints.addFeature(vanFeature);
|
||||
|
||||
const trackedPointsLayer = new VectorLayer({
|
||||
source: vectorSourceTrackedPoints,
|
||||
style: styles.iconVan
|
||||
});
|
||||
|
||||
//
|
||||
// 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,
|
||||
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;
|
||||
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,
|
||||
}),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function createPopoverHtml(feature) {
|
||||
const container = document.createElement('div');
|
||||
const title = document.createElement('div');
|
||||
title.textContent = feature.get('name');
|
||||
container.append(title);
|
||||
return container.innerHTML;
|
||||
// if (feature.get('trackable')) {
|
||||
// const linkParent = document.createElement('div');
|
||||
// const followLink = document.createElement('a');
|
||||
// followLink.textContent = 'Follow';
|
||||
// followLink.href = '#';
|
||||
// followLink.addEventListener('click', startFollowing(feature, followLink));
|
||||
// linkParent.append(followLink);
|
||||
// container.append(linkParent);
|
||||
// }
|
||||
}
|
||||
//
|
||||
// Route
|
||||
//
|
||||
|
||||
// display popup on click
|
||||
map.on('click', function (evt) {
|
||||
const feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) {
|
||||
return feature;
|
||||
});
|
||||
disposePopover();
|
||||
if (!feature) {
|
||||
return;
|
||||
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));
|
||||
}
|
||||
popup.setPosition(evt.coordinate);
|
||||
popover = new bootstrap.Popover(popupEl, {
|
||||
placement: 'top',
|
||||
html: true,
|
||||
content: createPopoverHtml(feature)
|
||||
for (const stage of stagesAhead) {
|
||||
vectorSourceStagesAhead.addFeature(new GeoJSON().readFeature(stage));
|
||||
}
|
||||
|
||||
const stagesCompletedLayer = new VectorLayer({
|
||||
source: vectorSourceStagesCompleted,
|
||||
style: styles.lineOrange
|
||||
});
|
||||
popover.show();
|
||||
});
|
||||
|
||||
// change mouse cursor when over marker
|
||||
map.on('pointermove', function (evt) {
|
||||
map.getTargetElement().style.cursor = map.hasFeatureAtPixel(evt.pixel)
|
||||
? 'pointer'
|
||||
: '';
|
||||
});
|
||||
const stagesAheadLayer = new VectorLayer({
|
||||
source: vectorSourceStagesAhead,
|
||||
style: styles.lineGrey
|
||||
});
|
||||
|
||||
// Close the popup when the map is moved
|
||||
map.on('movestart', disposePopover);
|
||||
//
|
||||
// Points of Interest
|
||||
//
|
||||
|
||||
const vectorSourcePOI = new VectorSource({
|
||||
features: new GeoJSON().readFeatures(geojsonPOI),
|
||||
});
|
||||
|
||||
//
|
||||
// Tracking
|
||||
//
|
||||
const poiLayer = new VectorLayer({
|
||||
source: vectorSourcePOI,
|
||||
style: styles.circleBlack,
|
||||
});
|
||||
|
||||
const updateInterval = 5000;
|
||||
let followedFeature = vanFeature;
|
||||
let followedZoomed = false;
|
||||
// let followedFeature = null;
|
||||
const vectorSourceTrackedPoints = new VectorSource();
|
||||
|
||||
function startFollowing(feature, followLink) {
|
||||
followedFeature = feature;
|
||||
followLink.textContent = 'Stop following';
|
||||
// followLink.removeEventListener('click', startFollowing);
|
||||
followLink.addEventListener('click', stopFollowing(feature, followLink));
|
||||
}
|
||||
const vanFeature= new Feature({
|
||||
geometry: new Point([8.918618, 44.407408]),
|
||||
name: 'Support Van',
|
||||
trackable: true
|
||||
});
|
||||
|
||||
function stopFollowing(feature, followLink) {
|
||||
followedFeature = null;
|
||||
followedZoomed = false;
|
||||
followLink.textContent = 'Stop following';
|
||||
// followLink.removeEventListener('click', stopFollowing);
|
||||
followLink.addEventListener('click', startFollowing(feature, followLink));
|
||||
}
|
||||
vectorSourceTrackedPoints.addFeature(vanFeature);
|
||||
|
||||
function updateData(startInterval=false) {
|
||||
fetch('https://r2b22.kip.pe/last.json')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
const coords = [data.lon, data.lat];
|
||||
vanFeature.getGeometry().setCoordinates(coords);
|
||||
const trackedPointsLayer = new VectorLayer({
|
||||
source: vectorSourceTrackedPoints,
|
||||
style: styles.iconVan
|
||||
});
|
||||
|
||||
// let zoomLevel;
|
||||
// if (!followedZoomed) {
|
||||
// zoomLevel = 13;
|
||||
// followedZoomed = true;
|
||||
// }
|
||||
//
|
||||
// Map initialization
|
||||
//
|
||||
|
||||
// if (followedFeature) {
|
||||
// view.animate({
|
||||
// center: followedFeature.getGeometry().getCoordinates(),
|
||||
// duration: 500,
|
||||
// zoom: zoomLevel
|
||||
// });
|
||||
// }
|
||||
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,
|
||||
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);
|
||||
|
||||
if (startInterval) {
|
||||
setInterval(updateData, updateInterval);
|
||||
//
|
||||
// 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;
|
||||
// if (feature.get('trackable')) {
|
||||
// const linkParent = document.createElement('div');
|
||||
// const followLink = document.createElement('a');
|
||||
// followLink.textContent = 'Follow';
|
||||
// followLink.href = '#';
|
||||
// followLink.addEventListener('click', startFollowing(feature, followLink));
|
||||
// linkParent.append(followLink);
|
||||
// container.append(linkParent);
|
||||
// }
|
||||
}
|
||||
|
||||
// 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 = 5000;
|
||||
// let followedFeature = vanFeature;
|
||||
// let followedZoomed = false;
|
||||
// let followedFeature = null;
|
||||
|
||||
function startFollowing(feature, followLink) {
|
||||
followedFeature = feature;
|
||||
followLink.textContent = 'Stop following';
|
||||
// followLink.removeEventListener('click', startFollowing);
|
||||
followLink.addEventListener('click', stopFollowing(feature, followLink));
|
||||
}
|
||||
|
||||
function stopFollowing(feature, followLink) {
|
||||
followedFeature = null;
|
||||
followedZoomed = false;
|
||||
followLink.textContent = 'Stop following';
|
||||
// followLink.removeEventListener('click', stopFollowing);
|
||||
followLink.addEventListener('click', startFollowing(feature, followLink));
|
||||
}
|
||||
|
||||
function updateData(startInterval=false) {
|
||||
fetch('https://r2b22.kip.pe/last.json')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
const coords = [data.lon, data.lat];
|
||||
vanFeature.getGeometry().setCoordinates(coords);
|
||||
|
||||
// let zoomLevel;
|
||||
// if (!followedZoomed) {
|
||||
// zoomLevel = 13;
|
||||
// followedZoomed = true;
|
||||
// }
|
||||
|
||||
// if (followedFeature) {
|
||||
// view.animate({
|
||||
// center: followedFeature.getGeometry().getCoordinates(),
|
||||
// duration: 500,
|
||||
// zoom: zoomLevel
|
||||
// });
|
||||
// }
|
||||
});
|
||||
|
||||
if (startInterval) {
|
||||
setInterval(updateData, updateInterval);
|
||||
}
|
||||
}
|
||||
|
||||
updateData(true);
|
||||
}
|
||||
|
||||
updateData(true);
|
||||
main();
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "ol-vite",
|
||||
"version": "1.2.1",
|
||||
"version": "1.3.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ol-vite",
|
||||
"version": "1.2.1",
|
||||
"version": "1.3.0",
|
||||
"dependencies": {
|
||||
"ol": "latest"
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "map",
|
||||
"version": "1.2.1",
|
||||
"version": "1.3.0",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"build": "vite build",
|
||||
|
|
Loading…
Reference in New Issue