simplemap/public/index.html

683 lines
24 KiB
HTML
Raw Normal View History

2024-01-01 21:40:47 +02:00
<!DOCTYPE html>
<!--suppress JSVoidFunctionReturnValueUsed -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Simple Map Paint</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/notyf@3/notyf.min.css">
<!-- <script>L_NO_TOUCH = true;</script>-->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/gokertanrisever/leaflet-ruler@master/src/leaflet-ruler.css"
crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/gh/gokertanrisever/leaflet-ruler@master/src/leaflet-ruler.js"
crossorigin="anonymous"></script>
<script src="https://unpkg.com/leaflet-auto-graticule@1.1.2/dist/L.AutoGraticule.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet-minimap/3.6.1/Control.MiniMap.min.css"
integrity="sha512-qm+jY0iQ4Xf5RL79UB75REDLYD0jtvxxVZp2RVIW8sm8RNiHdeN43oksqUPrBIshJtQcVPrAL08ML2Db8fFZiA=="
crossorigin="anonymous" referrerpolicy="no-referrer"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-minimap/3.6.1/Control.MiniMap.js"
integrity="sha512-ceQPs2CHke3gSINLt/JV37W1rfJOM64yuH999hnRhTP7tNtcSBp5hlTKhn8CEIhsFweSBrZMPVotAKjoyxGWNg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!--<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet-extra-markers/1.2.2/css/leaflet.extra-markers.min.css" integrity="sha512-wurszDyO1nj6ESdfrXb9h1hmoHMu3sQ3iXgKcu/p81lT+KaPGkta9NIPX7k6XXGgVYpcRHcc8AA4UIeQ7Ax/Cw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-extra-markers/1.2.2/js/leaflet.extra-markers.min.js" integrity="sha512-gGvL9Bo9cO1AY2+HRK8q4+d9yvy7z7dHhLL2YRFdoV7xZSP8wtGZltrmNDvBzIiwaBsybPmg5od7X6BlZh4kKg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="/Leaflet.EdgeMarker.js"></script>-->
<script src="https://cdn.jsdelivr.net/npm/notyf@3/notyf.min.js"></script>
<style>
body {
margin: 0;
touch-action: none;
font-family: Arial, Helvetica, sans-serif;
}
#placeholder {
position: absolute;
width: 100vw;
height: 100vh;
background-color: #a4a4a4;
color: black;
font-size: 16px;
font-family: monospace;
z-index: 99999;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
#map {
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
height: 100vh;
touch-action: none;
width: 100vw;
}
#log {
position: absolute;
width: 870px;
color: #fafafa;
right: 0;
font-family: monospace;
background-color: rgba(0, 0, 0, 0.5);
/*display: none;*/
z-index: 99999;
}
#users {
position: absolute;
width: 100vw;
color: #fafafa;
bottom: 0;
vertical-align: middle;
font-family: monospace;
background-color: rgba(0, 0, 0, 0.5);
/*display: none;*/
z-index: 99999;
line-height: 26px;
display: flex;
justify-content: center;
font-size: 20px;
}
#hint {
position: absolute;
width: 200px;
color: #fafafa;
top: 0;
left: 50px;
vertical-align: middle;
font-family: monospace;
background-color: rgba(0, 0, 0, 0.5);
/*display: none;*/
z-index: 99999;
display: flex;
justify-content: center;
font-size: 11px;
}
</style>
</head>
<body>
<div id="placeholder">
loading...
</div>
<div id="users">
</div>
<div id="hint">
* Space - toggle drawing <br>
* 10m save for 500 last lines
</div>
<div id="map">
</div>
<script>
var notyf = new Notyf({
duration: 1000,
position: {
x: 'center',
y: 'bottom',
},
types: [
{
type: 'success',
background: 'green',
dismissible: false,
},
{
type: 'error',
background: 'indianred',
duration: 2000,
dismissible: false
},
{
type: 'info',
icon: {
className: 'notyf__icon--success',
tagName: 'i',
},
background: '#0096f6',
duration: 2000,
dismissible: false
}
]
});
var socket = io();
var randomColor;
var myId = 0;
const userDiv = document.getElementById('users');
L.Map.mergeOptions({
touchExtend: true
});
L.Map.TouchExtend = L.Handler.extend({
// @method initialize(): void
// Sets TouchExtend private accessor variables
initialize: function (map) {
this._map = map;
this._container = map._container;
this._pane = map._panes.overlayPane;
},
// @method addHooks(): void
// Adds dom listener events to the map container
addHooks: function () {
L.DomEvent.on(this._container, 'touchstart', this._onTouchStart, this);
L.DomEvent.on(this._container, 'touchend', this._onTouchEnd, this);
L.DomEvent.on(this._container, 'touchmove', this._onTouchMove, this);
L.DomEvent.on(this._container, 'touchcancel', this._onTouchCancel, this);
L.DomEvent.on(this._container, 'touchleave', this._onTouchLeave, this);
},
// @method removeHooks(): void
// Removes dom listener events from the map container
removeHooks: function () {
L.DomEvent.off(this._container, 'touchstart', this._onTouchStart, this);
L.DomEvent.off(this._container, 'touchend', this._onTouchEnd, this);
L.DomEvent.off(this._container, 'touchmove', this._onTouchMove, this);
L.DomEvent.off(this._container, 'touchcancel', this._onTouchCancel, this);
L.DomEvent.off(this._container, 'touchleave', this._onTouchLeave, this);
},
_touchEvent: function (e, type) {
// #TODO: fix the pageX error that is do a bug in Android where a single touch triggers two click events
// _filterClick is what leaflet uses as a workaround.
// This is a problem with more things than just android. Another problem is touchEnd has no touches in
// its touch list.
var touchEvent = {};
if (typeof e.touches !== 'undefined') {
if (!e.touches.length) {
if (type === 'touchend') {
this._map.fire(type, {
originalEvent: e
});
return;
}
return;
}
touchEvent = e.touches[0];
} else if (e.pointerType === 'touch') {
touchEvent = e;
if (!this._filterClick(e)) {
return;
}
} else {
return;
}
var containerPoint = this._map.mouseEventToContainerPoint(touchEvent),
layerPoint = this._map.mouseEventToLayerPoint(touchEvent),
latlng = this._map.layerPointToLatLng(layerPoint);
this._map.fire(type, {
latlng: latlng,
layerPoint: layerPoint,
containerPoint: containerPoint,
pageX: touchEvent.pageX,
pageY: touchEvent.pageY,
originalEvent: e
});
},
/** Borrowed from Leaflet and modified for bool ops **/
_filterClick: function (e) {
var timeStamp = (e.timeStamp || e.originalEvent.timeStamp),
elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
// are they closer together than 500ms yet more than 100ms?
// Android typically triggers them ~300ms apart while multiple listeners
// on the same event should be triggered far faster;
// or check if click is simulated on the element, and if it is, reject any non-simulated events
if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
L.DomEvent.stop(e);
return false;
}
L.DomEvent._lastClick = timeStamp;
return true;
},
_onTouchStart: function (e) {
if (!this._map._loaded) {
return;
}
var type = 'touchstart';
this._touchEvent(e, type);
},
_onTouchEnd: function (e) {
if (!this._map._loaded) {
return;
}
var type = 'touchend';
this._touchEvent(e, type);
},
_onTouchCancel: function (e) {
if (!this._map._loaded) {
return;
}
var type = 'touchcancel';
if (this._detectIE()) {
type = 'pointercancel';
}
this._touchEvent(e, type);
},
_onTouchLeave: function (e) {
if (!this._map._loaded) {
return;
}
var type = 'touchleave';
this._touchEvent(e, type);
},
_onTouchMove: function (e) {
if (!this._map._loaded) {
return;
}
var type = 'touchmove';
this._touchEvent(e, type);
},
});
L.Map.addInitHook('addHandler', 'touchExtend', L.Map.TouchExtend);
var map = L.map('map', {
dragging: true,
doubleClickZoom: false,
minZoom: 4,
}).setView([48.8659904, 31.3847594], 7);
L.Map.mergeOptions({
touchExtend: true
});
L.control.ruler().addTo(map);
new L.AutoGraticule().addTo(map);
var Temp = L.tileLayer(
"https://tile.openweathermap.org/map/temp_new/{z}/{x}/{y}.png?appid=d22d9a6a3ff2aa523d5917bbccc89211",
{
maxZoom: 18,
attribution: '&copy; <a href="http://owm.io">VANE</a>',
id: "temp"
}
),
Precipitation = L.tileLayer(
"https://tile.openweathermap.org/map/precipitation_new/{z}/{x}/{y}.png?appid=d22d9a6a3ff2aa523d5917bbccc89211",
{
maxZoom: 18,
attribution: '&copy; <a href="http://owm.io">VANE</a>'
}
),
Wind = L.tileLayer(
"https://tile.openweathermap.org/map/wind_new/{z}/{x}/{y}.png?appid=d22d9a6a3ff2aa523d5917bbccc89211",
{
maxZoom: 18,
attribution: '&copy; <a href="http://owm.io">VANE</a>'
}
),
Pressure = L.tileLayer(
"https://tile.openweathermap.org/map/pressure_new/{z}/{x}/{y}.png?appid=d22d9a6a3ff2aa523d5917bbccc89211",
{
maxZoom: 18,
attribution: '&copy; <a href="http://owm.io">VANE</a>'
}
),
Clouds = L.tileLayer(
"https://tile.openweathermap.org/map/clouds_new/{z}/{x}/{y}.png?appid=d22d9a6a3ff2aa523d5917bbccc89211",
{
maxZoom: 18,
attribution: '&copy; <a href="http://owm.io">VANE</a>'
}
);
//Temp.addTo(map);
const getMapLocation = function (zoom, center) {
'use strict';
zoom = (zoom || zoom === 0) ? zoom : 7;
center = (center) ? center : [48.8659904, 31.3847594];
if (window.location.hash !== '') {
var hash = window.location.hash.replace('#', '');
var parts = hash.split(',');
if (parts.length === 3) {
center = {
lat: parseFloat(parts[0]),
lng: parseFloat(parts[1])
};
zoom = parseInt(parts[2].slice(0, -1), 10);
}
}
return {zoom: zoom, center: center};
}
var hash = getMapLocation();
map.setView(hash.center, hash.zoom);
map.on('moveend', function () {
var center = map.getCenter();
var hash = '#' +
Math.round(center.lat * 100000) / 100000 + ',' +
Math.round(center.lng * 100000) / 100000 + ',' +
map.getZoom() + 'z';
var state = {
zoom: map.getZoom(),
center: center
};
window.history.pushState(state, 'map', hash);
});
var OpenStreetMap_Mapnik = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
});
var OpenTopoMap = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
maxZoom: 17,
attribution: 'Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
});
var Esri_WorldImagery = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
});
var Stamen_Toner = L.tileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}{r}.{ext}', {
attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
subdomains: 'abcd',
minZoom: 0,
maxZoom: 20,
ext: 'png'
});
OpenStreetMap_Mapnik.addTo(map);
var OpenStreetMap_Mapnik2 = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
});
var miniMap = new L.Control.MiniMap(OpenStreetMap_Mapnik2).addTo(map);
var layerControl = L.control.layers({
"OSM": OpenStreetMap_Mapnik,
"OSM Topo": OpenTopoMap,
"ERSI Sat": Esri_WorldImagery,
"Contrast": Stamen_Toner
}, {
Temperature: Temp,
Precipitation: Precipitation,
Clouds: Clouds,
Pressure: Pressure,
Wind: Wind
}).addTo(map);
var paintMode = false;
var myPolyline;
var dr = false;
var loading = document.getElementById('placeholder');
socket.on('draw', function (msg) {
var polyline = L.polyline(msg.line, {color: msg.color}).addTo(map);
});
loading.innerHTML = "Loading history...";
socket.once('history', function (lines) {
lines.forEach(line => {
var polyline = L.polyline(line.line, {color: line.color}).addTo(map);
})
loading.innerHTML = "History loaded...";
loading.style.display = 'none';
});
socket.on('color', function (user) {
randomColor = user.color;
myId = user.id;
loading.innerHTML = "Color generated...";
});
/*var markers = L.layerGroup([]);
markers.addTo(map);
var myMakerIcon;
L.edgeMarker({
icon: L.icon({ // style markers
iconUrl: 'edge-arrow-marker-black.png',
clickable: true,
iconSize: [48, 48],
iconAnchor: [24, 24]
}),
rotateIcons: true,
layerGroup: markers
}).addTo(map);*/
socket.on('users', function (users) {
userDiv.innerHTML = "";
const currentUserIndex = users.findIndex(user => user.id === myId);
if (currentUserIndex !== -1) {
const currentUser = users.splice(currentUserIndex, 1)[0];
users.sort((a, b) => a.id - b.id);
users.unshift(currentUser);
}
users.forEach((user, index) => {
if (index === 0) {
/*myMakerIcon = L.ExtraMarkers.icon({ //SET MY MARKER ICON
icon: 'fa-number',
svg: true,
markerColor: randomColor,
shape: 'circle',
prefix: ''
});*/
userDiv.innerHTML += 'You: ';
userDiv.innerHTML += `<input type="color" id="you" style="width: 26px; margin-right: 3px; border: 2px solid ${user.color}; height: 26px; border-radius: 50%; display: inline-block" value="${user.color}">`;
// userDiv.innerHTML += `<div id="user-${user.id}" class="${cl}" title="${title}" style="background-color: ${user.color}; width: 20px; margin-right: 3px; height: 20px; border-radius: 50%; display: inline-block"></div>`;
userDiv.innerHTML += 'Others: ';
} else {
notyf.open({
type: 'info',
message: 'User connected'
});
userDiv.innerHTML += `<div id="user-${user.id}" class="other" title="User ${user.id}" style="background-color: ${user.color}; margin-top: 3px; width: 20px; margin-right: 3px; height: 20px; border-radius: 50%; display: inline-block"></div>`;
}
})
loading.innerHTML = "Users ...";
});
/*socket.on('new_marker', function (marker) {
var newMarker = L.marker([marker.coords.lat, marker.coords.lng], {
draggable: true,
autoPan: false,
icon: L.ExtraMarkers.icon({ // style markers
icon: 'fa-number',
svg: true,
markerColor: marker.color,
shape: 'circle',
prefix: ''
})
});
newMarker.addTo(markers);
// newMarker.on("dragend", function () {
// socket.emit('new_marker', {"color": randomColor, "user": myId, "coords": newMarker.getLatLng()});
// });
});*/
var changedColor = false;
userDiv.addEventListener('click', function (event) {
if (event.target.id === 'you') {
const userElement = event.target;
userElement.addEventListener('input', colorChangeEvent => {
const selectedColor = colorChangeEvent.target.value;
randomColor = selectedColor;
changedColor = true;
});
}
});
map.on('mousedown touchstart pointerdown', function (e) {
if ('originalEvent' in e) {
if (e.originalEvent.button === 1) {
map.dragging.enable();
return;
}
}
if (changedColor === true) {
socket.emit('new_color', {"color": randomColor, "user": myId});
changedColor = false;
}
if (paintMode) {
map.dragging.disable();
myPolyline = L.polyline([], {color: randomColor}).addTo(map);
}
dr = true;
});
map.on('mouseup touchend pointerup touchleave', function (e) {
dr = false;
if (typeof myPolyline !== 'undefined' && null !== myPolyline) {
socket.emit('draw', {"line": myPolyline.getLatLngs(), "user": myId});
}
})
map.on('mousemove touchmove pointermove', function (e) {
// console.log('mousemove')
if (paintMode && dr) {
if (e.type === 'touchmove' && e.originalEvent.touches.length > 1) {
return;
}
myPolyline.addLatLng(e.latlng);
}
})
document.body.onkeyup = function (e) {
if (e.key == " " ||
e.code == "Space" ||
e.keyCode == 32
) {
var container = document.getElementsByClassName('draw-paint')[0];
paintMode = !paintMode;
if (paintMode) {
container.style.backgroundColor = '#bfea90';
map.dragging.disable();
notyf.success("Paint Mode <b>ENABLED</b>");
} else {
container.style.backgroundColor = 'white';
map.dragging.enable();
notyf.success("Paint Mode <b style='color: #ffa6a6'>DISABLED</b>");
}
}
}
var drawPaint = L.Control.extend({
options: {
position: 'topleft'
},
onAdd: function (map) {
var container = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-custom draw-paint');
container.style.backgroundColor = 'white';
container.style.backgroundImage = "url(https://assada.dead.guru/storage/images/image_2023_08_16_16_46_52.png)";
container.style.backgroundSize = "30px 30px";
container.style.width = '30px';
container.style.height = '30px';
container.setAttribute('title', "Toggle drawing");
container.onclick = function () {
paintMode = !paintMode;
if (paintMode) {
map.dragging.disable();
container.style.backgroundColor = '#bfea90';
notyf.success("Paint Mode <b>ENABLED</b>");
} else {
map.dragging.enable();
container.style.backgroundColor = 'white';
notyf.success("Paint Mode <b style='color: #ffa6a6'>DISABLED</b>");
}
}
return container;
}
});
/*
var drawMarker = L.Control.extend({
options: {
position: 'topleft'
},
onAdd: function (map) {
var container = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-custom draw-paint');
container.style.backgroundColor = 'white';
container.style.backgroundImage = "url(https://assada.dead.guru/storage/images/image_2023_08_16_16_46_52.png)";
container.style.backgroundSize = "30px 30px";
container.style.width = '30px';
container.style.height = '30px';
container.setAttribute('title', "Add marker");
container.onclick = function () {
notyf.success("You can move marker by drag and drop");
var newMarker = L.marker(map.getCenter(), {
draggable: true,
autoPan: false,
icon: myMakerIcon
});
newMarker.addTo(markers);
newMarker.on("dragend", function () {
socket.emit('new_marker', {"color": randomColor, "user": myId, "coords": newMarker.getLatLng()});
});
socket.emit('new_marker', {"color": randomColor, "user": myId, "coords": newMarker.getLatLng()});
}
return container;
}
});*/
map.addControl(new drawPaint());
// map.addControl(new drawMarker());
</script>
</body>
</html>