galadrielmap_sk
Version:
a server-based chartplotter navigation software for pleasure crafts, motorhomes, and off-road cars. It's can be used on tablets and smartphones without install any app. Only browser need.
1,009 lines (960 loc) • 121 kB
HTML
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" > <!-- tell the mobile browser to disable unwanted scaling of the page and set it to its actual size -->
<script src="internationalisation/internationalisation.js" ></script> <!-- там определяются переменные, используемые в загружаемых скриптах -->
<!-- Leaflet -->
<link rel="stylesheet" href="leaflet/leaflet.css" type="text/css">
<script src="leaflet/leaflet.js"></script>
<script src="Leaflet.RotatedMarker/leaflet.rotatedMarker.js"></script>
<!-- Leaflet sidebar -->
<link rel="stylesheet" href="leaflet-sidebar-v2/css/leaflet-sidebar.min.css" />
<script src="leaflet-sidebar-v2/js/leaflet-sidebar.min.js"></script>
<script src="polycolor/polycolorRenderer.js"></script>
<script src="value2color/value2color.js"></script>
<link rel="stylesheet" href="leaflet-omnivorePATCHED/leaflet-omnivore.css" />
<script src="leaflet-omnivorePATCHED/leaflet-omnivore.js"></script>
<script src="Leaflet.Editable/src/Leaflet.Editable.js"></script>
<link rel="stylesheet" href="leaflet-measure-path/leaflet-measure-path.css" />
<script src="leaflet-measure-path/leaflet-measure-path.js"></script>
<script src="L.TileLayer.Mercator/src/L.TileLayer.Mercator.js"></script>
<script src="supercluster/dist/supercluster.js"></script>
<link rel="stylesheet" href="leaflet-tracksymbolPATCHED/leaflet-tracksymbol.css"/>
<script src="leaflet-tracksymbolPATCHED/leaflet-tracksymbol.js"></script>
<script src="coordinate-parserPATCHED/coordinates.js"> </script>
<script src="coordinate-parserPATCHED/validator.js"></script>
<script src="coordinate-parserPATCHED/coordinate-number.js"></script>
<script src="long-press-event/dist/long-press-event.min.js"></script>
<script src="Leaflet.TextPath/leaflet.textpath.js"></script>
<link rel="stylesheet" href="galadrielmap.css" type="text/css"> <!-- замена стилей -->
<script src="galadrielmap.js"></script>
<script src="options.js"></script>
<title>GaladrielMap SignalK ed.</title>
<!-- карта на весь экран -->
<style>
body {
padding: 0;
margin: 0;
}
html, body, #mapid {
height: 100%;
width: 100vw;
}
</style>
</head>
<body>
<div id="sidebar" class="leaflet-sidebar collapsed">
<!-- Nav tabs -->
<div class="leaflet-sidebar-tabs">
<ul role="tablist" id="featuresList">
<li id="homeTab"><a href="#home" role="tab"><img src="img/maps.svg" alt="menu" width="70%"></a></li>
<li id="dashboardTab"><a href="#dashboard" role="tab"><img src="img/speed1.svg" alt="dashboard" width="70%"></a></li>
<li id="tracksTab"><a href="#tracks" role="tab"><img src="img/track.svg" alt="tracks" width="70%"></a></li>
<li id="measureTab" ><a href="#measure" role="tab"><img src="img/route.svg" alt="Create route" width="70%"></a></li>
<li id="routesTab"><a href="#routes" role="tab"><img src="img/poi.svg" alt="Routes and POI" width="70%"></a></li>
</ul>
<ul role="tablist" id="settingsList">
<li id="MOBtab" style="margin-bottom:1.5em;"><a href="#MOB" role="tab"><img src="img/mob.svg" alt="activate MOB" width="70%"></a></li>
<li><a href="#settings" role="tab"><img src="img/settings1.svg" alt="settings" width="70%"></a></li>
</ul>
</div>
<!-- Tab panes -->
<div class="leaflet-sidebar-content" id='tabPanes'>
<!--
<div id='infoBox' style='font-size: 90%; position: absolute;'>
</div>
<script>
infoBox.innerText='width: '+window.outerWidth+' height: '+window.outerHeight;
</script>
-->
<!-- Карты -->
<div class="leaflet-sidebar-pane" id="home" style="margin:0;padding:0;height:100%;overflow:hidden;">
<div style="margin:0;padding:0;height:100%;overflow:hidden;display:flex;flex-direction:column;">
<h1 class="leaflet-sidebar-header leaflet-sidebar-close"> <span id="homeHeaderTXT"></span> <span class="leaflet-sidebar-close-icn"><img src="img/Triangle-left.svg" alt="close" height="100%"></span></h1>
<ul id="mapDisplayed" class="commonList" style="height:10%;overflow:auto;"><?php // overflow не наследуется, но если здесь не указать - горизонтальной прокрутки не будет, как это указано в основном стиле для leaflet-sidebar-pane ?>
</ul>
<ul id="mapList" class='commonList' style="overflow:auto;height:90%;">
<li hiden class="template" onClick="{selectMap(event.currentTarget)}"></li>
</ul>
<div style="width:95%;height:1.5rem;text-align:center;margin:0 0 2px;padding: 0;">
<button id="showMapsToggler" onClick='showMapsToggle();' style="width:90%;height:100%;"></button>
</div>
</div>
</div>
<!-- Приборы -->
<div class="leaflet-sidebar-pane" id="dashboard" style="height:95%;">
<h1 class="leaflet-sidebar-header leaflet-sidebar-close"> <span id="dashboardHeaderTXT"></span> <span class="leaflet-sidebar-close-icn"><img src="img/Triangle-left.svg" alt="close" height="100%"></span></h1>
<div class="big_symbol centred_pane">
<div>
<div style="line-height:0.6;" onClick="map.setView(cursor.getLatLng());">
<div><span id="dashboardSpeedTXT"></span></div><br>
<div id='velocityDial' style="font-size:200%;"></div><br>
<div><span id="dashboardSpeedMesTXT"></span></div>
</div>
<div id='depthDial' style="line-height:0.6;" onClick="map.setView(cursor.getLatLng());">
</div>
<div style="line-height:0.9;" onClick="map.setView(cursor.getLatLng());">
<br><span><span id="dashboardCourseTXT"></span></span>
<span style="font-size:50%; "><br><span id="dashboardCourseAltTXT"></span></span>
<div style="font-size:200%;">
<span id='courseDisplay'></span>
</div>
</div>
<div style="line-height:1;" onClick="doCopyToClipboard(lat+' '+lng);" >
<br><span id="mobPosTXT"></span><br>
<span style="font-size:50%;" id="mobPosAltTXT"></span>
<div style="font-size:150%;" onClick="doCopyToClipboard(lat+' '+lng);">
<span id='locationDisplay'></span>
</div>
</div>
</div>
</div>
<div id="positionTimeDisplay" style="float:left;position:relative;bottom:90%;left:-1rem;"></div>
<div style="text-align:center; position: absolute; bottom: 0;margin:0 0.5rem;">
<span id="dashboardSpeedZoomTXT"></span> <span id='velocityVectorLengthInMnDisplay'></span> <span id="dashboardSpeedZoomMesTXT"></span>.
</div>
</div>
<!-- Треки -->
<div class="leaflet-sidebar-pane" id="tracks" style="overflow:hidden;">
<h1 class="leaflet-sidebar-header leaflet-sidebar-close"> <span id="tracksHeaderTXT"></span> <span class="leaflet-sidebar-close-icn"><img src="img/Triangle-left.svg" alt="close" height="100%"></span></h1>
<div style="margin:0 1rem;float:right;">
<div class="onoffswitch" style="float:right;margin: 1rem auto;">
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="loggingSwitch" onChange="loggingRun();" >
<label class="onoffswitch-label" for="loggingSwitch">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
<div style="padding:1rem 0.5rem 0 0;font-size:120%;text-align:center;">
<span id="loggingIndicator" style="font-size:100%;"></span> <span id="loggingTXT"></span>
</div>
<div style="width:100%;overflow-x:auto;"><!-- Здесь только горизонтальная прокрутка, а вертикальная - в объемлющем div -->
<ul id="trackDisplayed" class='commonList'>
</ul>
<ul id="trackList" class='commonList'>
<li hidden class="template" onClick='{selectTrack(event.currentTarget,trackList,trackDisplayed,displayTrack)}' id='trackLiTemplate' class='currentTrackName' title=''></li>
</ul>
</div>
</div>
<!-- Расстояния -->
<div class="leaflet-sidebar-pane" id="measure" style="padding-bottom:1rem;">
<h1 class="leaflet-sidebar-header leaflet-sidebar-close"> <span id="measureHeaderTXT"></span> <span class="leaflet-sidebar-close-icn"><img src="img/Triangle-left.svg" alt="close" height="100%"></span></h1>
<!-- Кнопки создания/редактирования маршрута -->
<div id='routeControls' class="routeControls" style="width:94%; margin:1em 0.5rem;text-align:center;">
<input type="radio" name="routeControl" class='L' id="routeCreateButton"
onChange="
startEditing();
//WPTbuttonsReady(); // Может быть, оно не надо? Кнопка Следовать появится после повторной активации режима редактирования. Типа, после завершения рисования...
"
>
<label for="routeCreateButton"><span id="routeControlsBeginTXT"></span></label>
<input type="radio" name="routeControl" class='R' id="routeContinueButton"
onChange="
// по нажатию кнопки создаётся однократно срабатываемый обработчик клика
// на вершине объекта editable
map.once('editable:vertex:click', function f(e) { // это CancelableVertexEvent
//console.log(e);
//console.log(e.vertex);
e.cancel(); // прекратить дальнейшую обработку
//e.vertex.split();
e.vertex.continue();
routeCreateButton.checked=true;
});
"
>
<label for="routeContinueButton"><span id="routeControlsContinueTXT"></span></label><br>
<div id='pointsButtons'>
<br>
<button id='ButtonSetpoint' onClick='createEditableMarker(pointIcon);' class='pointButton'><img src="leaflet-omnivorePATCHED/symbols/point.png" alt="ok" width="100%"></button>
<button id='ButtonSetanchor' onClick='createEditableMarker(anchorIcon);' class='pointButton'><img src="leaflet-omnivorePATCHED/symbols/anchor.png" alt="ok" width="100%"></button>
<button id='ButtonSetcaution' onClick='createEditableMarker(cautionIcon);' class='pointButton'><img src="leaflet-omnivorePATCHED/symbols/caution.png" alt="ok" width="100%"></button><br>
<br>
</div>
<input id = 'editableObjectName' type="text" title="" placeholder='' size='255' style='width:95%;font-size:120%;'><br>
<textarea id = 'editableObjectDescr' title="" rows='3' cols='255' placeholder='' style='width:95%;'></textarea><br>
<input id='editableObjectLeafletID' type="hidden">
<input type="radio" name="routeControl" id="routeEraseButton" class='M' onChange="eraseEditable();">
<label for="routeEraseButton"><span id="routeControlsClearTXT"></span></label>
<!--
<div style="margin: 2.5em 0;text-align: center;width:96%;">
<button id="prevWPTbutton" disabled onClick="prevWPT();" style="font-size:200%;padding:0 0.2em;">◁</button>
<button id="followWPTbutton" disabled style="vertical-align:top;padding:0.7em 1.3em;margin:0.1em 0.5em;"><?php echo $followWPTbuttonTXT;?></button>
<button id="nextWPTbutton" disabled onClick="nextWPT();" style="font-size:200%;padding:0 0.2em;">▷</button>
</div>
-->
</div>
<!-- Поиск места -->
<div style="width:95%;margin:1em 0.5rem;">
<div style="margin:0;padding:0;">
<button onClick='goToPositionField.value = "";goToPositionField.focus();' style="width:2rem;height:1rem;margin:0 0.7rem 0 0;float:right;"><img src="img/no.svg" title="Clear" alt="Clear" width="8px" style="vertical-align:top;"></button>
<button onClick='goToPositionField.value += "°";goToPositionField.focus();' style="width:2rem;height:1rem;margin:0 0.7rem 0 0;"><span style="font-weight: bold; font-size:150%;">°</span></button>
<button onClick='goToPositionField.value += "′";goToPositionField.focus();' style="width:2rem;height:1rem;margin:0 0.7rem 0 0;"><span style="font-weight: bold; font-size:150%;">′</span></button>
<button onClick='goToPositionField.value += "″";goToPositionField.focus();' style="width:2rem;height:1rem;margin:0 0rem 0 0;"><span style="font-weight: bold; font-size:150%;">″</span></button><br>
</div>
<span id="routePosTXT"></span><br>
<input id='goToPositionField' type="text" title="" size='12' style='width:70%;font-size:150%;'>
<button id='goToPositionButton' class='okButton' onClick='flyByString(goToPositionField.value);' style="float:right;"><img src="img/ok.svg" alt="Ok" style="width:var(--font-size);"></button><br>
</div>
<ul id='geocodedList' class='commonList' style="width:100%;max-height:23vh;overflow:auto;">
</ul>
<!-- Сохранение маршрута -->
<div style="width:94%;margin:1em 0.5rem;text-align:center;">
<h3 id="routeSaveTitle"></h3>
<input id = 'routeSaveName' type="text" title="" placeholder='' size='255' style='width:95%;font-size:120%;'>
<textarea id = 'routeSaveDescr' title="" rows='5' cols='255' placeholder='' style='width:95%;'></textarea>
<button onClick="DOsaveGPX();" type='submit' class='okButton' style="float:right;">
<img src="img/ok.svg" title="Ok" alt="Ok" style="width:var(--font-size);">
</button>
<button onClick='routeSaveName.value=""; routeSaveDescr.value="";' type='reset' class='okButton' style="float:left;">
<img src="img/no.svg" alt="Clear" style="width:var(--font-size);">
</button>
<div id="routeSaveMessage" style="margin: 1rem;"></div>
</div>
</div>
<!-- Места и маршруты -->
<div class="leaflet-sidebar-pane" id="routes">
<h1 class="leaflet-sidebar-header leaflet-sidebar-close"> <span id="routesHeaderTXT"></span> <span class="leaflet-sidebar-close-icn"><img src="img/Triangle-left.svg" alt="close" height="100%"></span></h1>
<div style="width:100%;overflow-x:auto;"><!-- Здесь только горизонтальная прокрутка, а вертикальная - в объемлющем div -->
<ul id="routeDisplayed" class='commonList'>
</ul>
<ul id="routeList" class='commonList'>
<li hidden class="template" onClick='{selectTrack(event.currentTarget,routeList,routeDisplayed,displayRoute)}'></li>
</ul>
</div>
</div>
<!-- MOB -->
<div class="leaflet-sidebar-pane" style="height:90%;" id="MOB">
<h1 class="leaflet-sidebar-header leaflet-sidebar-close" style="background-color:red;"><span id="mobTXT"></span><span class="leaflet-sidebar-close-icn"><img src="img/Triangle-left.svg" alt="close" height="100%"></span></h1>
<div style="margin:1rem 0;width:100%;text-align: center;">
<button onClick='MOBalarm();' style="width:75%;"><span style=""><span id="addMarkerTXT"></span></span></button>
</div>
<div class="big_symbol centred_pane" style="height:85%;overflow:auto;" onClick="map.setView(currentMOBmarker.getLatLng());"> <!-- передвинуть карту на место текущего маркера MOB -->
<div><!-- объемлющий div необходим -->
<div style="margin:0.5rem 0;">
<span style="display:block;" id="bearingTXT"></span>
<span style="font-size:75%;display:block;" id="altBearingTXT"></span>
<span style="font-size:200%;margin:0.5rem;display:block;" id='azimuthMOBdisplay'> </span>
</div>
<div style="margin:0.5rem 0;">
<span style="display:block;"><span id="distanceTXT"></span>, <span id="dashboardMeterMesTXT"></span></span>
<span style="font-size:75%;display:block;" id="altDistanceTXT"></span>
<span style="font-size:200%;margin:0.5rem;display:block;" id='distanceMOBdisplay'> </span>
<span style="font-size:120%;margin:0.5rem;display:block;" id='directionMOBdisplay'></span>
</div>
<div onClick="doCopyToClipboard(Math.round(currentMOBmarker.getLatLng().lat*10000)/10000+' '+Math.round(currentMOBmarker.getLatLng().lng*10000)/10000);" >
<span style="display:block;" id="dashboardPosTXT"></span>
<span style="font-size:75%;display:block;" id="dashboardPosAltTXT"></span>
<span style="font-size:130%;margin:0.3rem;display:block;" id='locationMOBdisplay'></span>
</div>
</div>
</div>
<div style="position: absolute; bottom: 1rem;width:100%;text-align: center;"> <!-- Отбой -->
<button onClick='delMOBmarker();' id='delMOBmarkerButton' style="width:75%;margin:1.5rem 0;" disabled ><span style="" id="removeMarkerTXT"></span></button><br>
<a style="position:relative;left:0rem;vertical-align:revert;color:gray;font-size:150%;" onClick='
this.nextElementSibling.disabled=false;
this.style.color="green";
'>◼</a>
<button onClick='realMOBclose();' style="position:relative;rigt:1rem;width:70%;" disabled><span style="" id="cancelMOBTXT"></span></button><br>
</div>
</div>
<!-- Настройки -->
<div class="leaflet-sidebar-pane" id="settings">
<h1 class="leaflet-sidebar-header leaflet-sidebar-close"><span id="settingsHeaderTXT"></span> <span class="leaflet-sidebar-close-icn"><img src="img/Triangle-left.svg" alt="close" height="100%"></span></h1>
<div style="margin: 0.7em 1em;"> <!-- Следование за курсором -->
<div class="onoffswitch" style="float:right;margin: 1rem auto;"> <!-- Переключатель https://proto.io/freebies/onoff/ -->
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="followSwitch" onChange="noFollowToCursor=!noFollowToCursor; CurrnoFollowToCursor=noFollowToCursor;" checked>
<label class="onoffswitch-label" for="followSwitch">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<span id="settingsCursorTXT"></span>
</div>
<br>
<div style="margin: 0.7em 1em;"> <!-- Текущий трек всегда показывается -->
<div class="onoffswitch" style="float:right;margin: 1rem auto;"> <!-- Переключатель https://proto.io/freebies/onoff/ -->
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="currTrackSwitch" onChange="loggingWait();" checked>
<label class="onoffswitch-label" for="currTrackSwitch">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<span id="settingsTrackTXT"></span>
</div>
<br>
<div style="margin: 0.7em 1em;"> <!-- Выбранные маршруты всегда показываются -->
<div class="onoffswitch" style="float:right;margin: 1rem auto;"> <!-- Переключатель https://proto.io/freebies/onoff/ -->
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="SelectedRoutesSwitch" onChange="" checked>
<label class="onoffswitch-label" for="SelectedRoutesSwitch">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<span id="settingsRoutesAlwaysTXT"></span>
</div>
<br>
<div style="margin: 0.7em 1em;"> <!-- Показывать окружности дистанции -->
<div class="onoffswitch" style="float:right;margin: 1rem auto;"> <!-- Переключатель https://proto.io/freebies/onoff/ -->
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="distCirclesSwitch" onChange="distCirclesToggler();" checked>
<label class="onoffswitch-label" for="distCirclesSwitch">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<span id="settingsdistCirclesTXT"></span>
</div>
<br>
<div style="margin: 0.7em 1em;"> <!-- Показывать символ ветра -->
<div class="onoffswitch" style="float:right;margin: 1rem auto;"> <!-- Переключатель https://proto.io/freebies/onoff/ -->
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="windSwitch" onChange="windSwitchToggler();" checked>
<label class="onoffswitch-label" for="windSwitch">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<span id="settingsdistWindTXT"></span>
</div>
<br>
<div style="margin: 3em 1em 0.1em;"> <!-- Показ целей AIS -->
<div class="onoffswitch" style="float:right;margin: 0 auto;"> <!-- Переключатель https://proto.io/freebies/onoff/ -->
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="DisplayAISswitch" onChange="watchAISswitching();">
<label class="onoffswitch-label" for="DisplayAISswitch">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<span id="DisplayAIS_TXT"></span>
</div>
<br>
<div style="margin: 3em 1em 0.1em;"> <!-- Сокрытие элементов управления -->
<div class="onoffswitch" style="float:right;margin: 0 auto;"> <!-- Переключатель https://proto.io/freebies/onoff/ -->
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="hideControlsSwitch" value="onoffswitch" onChange="hideControlsToggler(this);">
<label class="onoffswitch-label" for="hideControlsSwitch">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<span id="hideControlsSwitchTXT"></span><br><br>
<div>
<div style="float: right;">
<input style="width:1em;" type="radio" name="hideControlPosition" value="topleft" disabled onChange="hideControlsToggler(this);">
<input style="width:1em;" type="radio" name="hideControlPosition" value="topmiddle" disabled onChange="hideControlsToggler(this);">
<input style="width:1em;" type="radio" name="hideControlPosition" value="topright" disabled onChange="hideControlsToggler(this);"><br>
<input style="width:1em;" type="radio" name="hideControlPosition" value="leftmiddle" disabled onChange="hideControlsToggler(this);">
<input style="width:1em;float:right;" type="radio" name="hideControlPosition" value="rightmiddle" disabled onChange="hideControlsToggler(this);">
<span> </span><br>
<input style="width:1em;" type="radio" name="hideControlPosition" value="bottomleft" disabled onChange="hideControlsToggler(this);">
<input style="width:1em;" type="radio" name="hideControlPosition" value="bottommiddle" checked disabled onChange="hideControlsToggler(this);">
<input style="width:1em;" type="radio" name="hideControlPosition" value="bottomright" disabled onChange="hideControlsToggler(this);">
</div>
<span style="margin:100% 0 100% 0;" id="hideControlsPositionTXT"></span>
</div>
</div>
<br>
<div style="margin: 3em 1em 0.1em;"> <!-- максимальная скорость обновления -->
<div style="float:right;margin: 1rem auto;">
<input id='minWATCHintervalInput' type="text" pattern="[0-9]*" title="" size='4' style='width:3rem;font-size:175%;'
onChange="minWATCHinterval=parseFloat(this.value);
if(isNaN(minWATCHinterval)) minWATCHinterval=0;
//console.log('Изменение, minWATCHinterval',minWATCHinterval);
spatialWebSocketStop('Close socket to change WATCH interval');
watchAISstop('Close socket to change WATCH interval');
"
>
</div>
<span id="minWATCHintervalTXT"></span>
</div>
</div>
</div>
</div><!-- end sidebar -->
<div id="hideControl">
</div>
<div id="mapid" ></div>
</body>
<script> "use strict";
// Глобальные переменные
// для загрузки Mapbox GL при необходимости. Из-за чего-то надо так.
var mapboxGLscript = null; // скрипт Mapbox GL, загружается при открытии соответствующей карты. Эти глобальные переменные ни нафиг не нужны, но если грузить скрипты Mapbox GL где-то в глубине -- при закрытии карты возникает мутная ошибка.
var mapboxLeafletscript = null; // скрипт mapbox-gl-leaflet
var mapboxCountourscript = null; // скрипт mapbox-contour
// Карта
var showMapsList = storageHandler.restore('showMapsList') || []; // массив названий избранных карт
var savedLayers = []; // массив для хранения объектов, когда они не на карте
var additionalTileCachePath = ''; // дополнительный кусок пути к тайлам между именем карты и /z/x/y.png Используется в версионном кеше, например, в погоде. Без / в конце, но с / в начале, либо пусто. Присваивается в javascriptOpen в параметрах карты. Или ещё где-нибудь.
var startCenter = storageHandler.restore('startCenter'); // storageHandler from galadrielmap.js
if(! startCenter) startCenter = L.latLng(defaultCenter); // начальная точка из options.js
var startZoom = storageHandler.restore('startZoom'); // storageHandler from galadrielmap.js
if(! startZoom) startZoom = 12; // начальный масштаб
var userMoveMap = true; // флаг для отделения собственных движений карты от пользовательских. Считаем все пользовательскими, и только где надо - выставляем иначе
// ГПС
var minWATCHinterval = storageHandler.restore('minWATCHinterval'); // Минимальный интервал, сек., с которым будут приходить данные от gpsdPROXY. Если 0 -- то по мере их получения от датчиков
if(!minWATCHinterval) minWATCHinterval = 0;
minWATCHintervalInput.value = minWATCHinterval;
if(PosFreshBefore < (2*minWATCHinterval*1000+1000)) PosFreshBefore = 2*minWATCHinterval*1000+1000; // PosFreshBefore в options.js
if(DepthFreshBefore < (2*minWATCHinterval*1000)) DepthFreshBefore = 2*minWATCHinterval*1000; // DepthFreshBefore в options.js
if(WindFreshBefore < (2*minWATCHinterval*1000)) WindFreshBefore = 2*minWATCHinterval*1000; // WindFreshBefore в options.js
var followToCursor = true; // карта следует за курсором Обеспечивает только паузу следования при перемещениях и масштабировании карты руками
var noFollowToCursor = false; // карта никогда не следует за курсором Глобальное отключение следования. Само не восстанавливается.
var CurrnoFollowToCursor = 1; // глобальная переменная для сохранения состояния
var followPause = 10 * 1000; // пауза следования карты за курсором, когда карту подвинули руками, микросекунд
var savePositionEvery = 10 * 1000; // будем сохранять положение каждые микросекунд локально в куку
var followPaused; // объект таймера, который восстанавливает следование курсору
if(!velocityVectorLengthInMn) velocityVectorLengthInMn = 10; // длинной в сколько минут пути рисуется линия скорости
// Окружности дистанции
distCirclesSwitch.checked = Boolean(storageHandler.restore('distCirclesSwitch')); // показывать окружности дистанции
// AIS
var vehicles = []; // list of visible by AIS data vehicle objects массив layers с целями
// Пути и маршруты
var editorEnabled = false; // семафор, что можно использовать редактирования
// Путь
var currentTrackServerURI = 'getlasttrkpt'; // адрес для подключения к сервису, отдающему сегменты текущего трека
var trackDirURI = 'track'; // адрес каталога с треками
var routeDirURI = 'route'; // адрес каталога с маршрутами
var currentTrackName = ''; // имя текущего (пишущегося сейчас) трека
var updateRouteServerURI = 'checkRoutes'; // url службы динамического обновления маршрутов
currTrackSwitch.checked = Boolean(storageHandler.restore('currTrackSwitch'));
SelectedRoutesSwitch.checked = Boolean(storageHandler.restore('SelectedRoutesSwitch')); // показывать выбранные маршруты
loggingSwitch.checked = Boolean(storageHandler.restore('loggingSwitch')); // storageHandler from galadrielmap.js
if(loggingSwitch.checked) loggingRun(); // запустим запись трека, если было указано запустить
var currentRoute; // объект Editable, по которому щёлкнули. Типа, текущий.
var globalCurrentColor = 0xFFFFFF; // цвет линий и значков кластеров после первого набора
var currentTrackShowedFlag = false; // флаг, не показывается ли текущий путь. Если об этом спрашивать у Leaflet, то пока загружается трек, можно запустить его загрузку ещё раз пять.
// Маршрут
var drivedPolyLineOptions;
var currentRoute; // L.layerGroup, по объекту Editable которого щёлкнули. Типа, текущий.
{let weight;
if(L.Browser.mobile && L.Browser.touch) weight = 13; // мобильный браузер
else weight = 9; // стационарный браузер
drivedPolyLineOptions = { options: {
showMeasurements: true, // включить показ расстояний
//color: '#FDFF00',
weight: weight,
opacity: 0.7,
},
feature: {type: 'Feature',
properties: { // типа, оно будет JSONLayer
isRoute: true // укажем, что это путь
},
},
};
}
var dravingLines = L.layerGroup(); // слои, в которых, собственно, рисуются маршруты и путевые точки
dravingLines.properties = {};
var goToPositionManualFlag = false; // флаг, что поле goToPositionField стали редактировать руками, и его не надо обновлять
var distCircles = []; // круги дистанции, массив L.circle. Обращение к этому массиву может происходить сразу после инициализации карты.
if(storageHandler.restore('WindSwitch') === null) windSwitch.checked = true; // показывать символ ветра
else windSwitch.checked = Boolean(storageHandler.restore('WindSwitch')); // getCookie from galadrielmap.js
// Dashboard
var lat; // широта
var lng; // долгота, округлённые до 4-х знаков
var controlsList = []; // список control для сокрытия их с экрана
// восстановление переключателя сокрытия всего
hideControlsSwitch.checked = Boolean(storageHandler.restore('hideControlsSwitch'));
if(storageHandler.restore('hideControlPosition')){
for(let radio of settings.querySelectorAll('input[type="radio"][name="hideControlPosition"]')){
if(radio.value == storageHandler.restore('hideControlPosition')) {
radio.checked = true;
}
else radio.checked = false;
};
};
// MOB
var currentMOBmarker;
// main output data
var upData = {};
//var vesselSelf = getCookie('GaladrielvesselSelf');
var vesselSelf = storageHandler.restore('vesselSelf'); // storageHandler from galadrielmap.js
//var instanceSelf = getCookie('GaladrielMapInstance'); // идентификатор экземпляра программы
var instanceSelf = storageHandler.restore('instanceSelf'); // storageHandler from galadrielmap.js
if(!instanceSelf) {
instanceSelf = '-' + generateUUID(); // - в начале - чтобы начало не совпало с mmsi
let expires = new Date();
expires.setTime(expires.getTime() + (24*60*60*1000)); // протухнет через сутки
//document.cookie = "GaladrielMapInstance="+instanceSelf+"; expires="+expires+"; path=/; SameSite=Lax;";
storageHandler.save('instanceSelf',instanceSelf);
}
// Кластеризация точек
var superclusterRadius = 40; // px
var lastSuperClusterUpdatePosition = [[0,0],0]; // [<LatLng>,<zoom>] точка и масштаб последнего пересчёта supercluster
//var collisionVessels = {}; ///////// for collision test purpose /////////
// Поехали
// подготовка интерфейса, списков карт и треков, etc.
onBodyLoad()
// Определим карту
var map = L.map('mapid', {
center: startCenter,
zoom: startZoom,
attributionControl: false,
zoomControl: false,
editable: true
}
);
// Controls
// Zoom в правом верхнем углу
controlsList.push(L.control.zoom({
position:'topright'
}).addTo(map));
// Версия и пр. в правом нижнем углу
controlsList.push(L.control.attribution({
prefix: '<a href="https://youtu.be/kwMt4rjgsJs" target=”_blank”><i>имевший цель, но чуждый смысла</i></a>'
}).addTo(map));
// Шкала масштаба
//controlsList.push(
L.control.scale({
position: 'bottomleft',
maxWidth: 200,
imperial: false
}).addTo(map)
//);
// control для записывания в clipboard
var copyToClipboard = new L.Control.CopyToClipboard({ // класс определён в galadrielmap.js
position: 'bottomright'
}); // на карту не добавляется
// Добавим копирование содержимого goToPositionField. На него навешивается много обработчиков
goToPositionField.addEventListener('long-press', function(e){doCopyToClipboard(goToPositionField.value);}); // при получении фокуса - прекратить обновление
// Панель управления
var sidebar = L.control.sidebar('sidebar',{
container: 'sidebar',
}).addTo(map);
controlsList.push(sidebar);
sidebar.on("content", function(event){ // Событие открытия? панели
switch(event.id){ // какую вкладку открыли
case 'tracks': // треки
loggingCheck();
break;
case 'measure': // рисование маршрута
centerMarkOn(); // включить крестик в середине
if(CurrnoFollowToCursor === 1)CurrnoFollowToCursor = noFollowToCursor; // запомним состояние глобального признака следования за курсором, если ещё не запоминали
noFollowToCursor = true; // отключим следование за курсором
editorEnabled = true; // разрешим редактирования
routeCreateButton.disabled=false; // - сделать доступной кнопку Начать
pointsControlsEnable(); // включим кнопки точек
break;
case 'routes': // треки
// обновим список маршрутов, асинхронно
listPopulate(routeList,routeDirURI,false,true,function(){
const routeListLi = routeList.querySelectorAll('li');
routeDisplayed.querySelectorAll('li').forEach(function (displayedLi){
//console.log('displayedLi:',displayedLi.id);
for(const li of routeListLi){
//console.log('\trouteList li',li.id);
if(displayedLi.id==li.id){
li.remove(); // method removes the element from the DOM. Объект остаётся в коллекции routeListLi? Похоже, да, хотя не должен? Тогда он будет убит сборщиком мусора только после смерти routeListLi
break;
};
};
});
});
break;
case 'MOB': // человек за бортом
if(!map.hasLayer(mobMarker)) {
MOBalarm();
MOBtabHighLight(true); // Подсветим кнопку
}
else if(!map.hasLayer(cursor)) centerMarkOn(); // включить крестик в середине
break;
}
});
sidebar.on("closing", function(){
if(CurrnoFollowToCursor !== 1) noFollowToCursor = CurrnoFollowToCursor; // восстановим признак следования за курсором
CurrnoFollowToCursor = 1;
centerMarkOff(); // выключить крестик посередине
if(currentRoute && delShapes()) editorEnabled='maybe'; // есть редактируемые слои
else {
editorEnabled=false; // если нет редактируемых слоёв -- запретим включать редактирования
currentRoute = null;
routeSaveName.value = '';
routeSaveDescr.value = '';
}
});
// Сокрытие всего
hideControlsToggler(hideControlsSwitch); // создаёт как-бы control, делающий невидимыми все control, перечисленные в controlsList
// end controls
// Поведение карты
map.on('movestart zoomstart', function(event) { // карту начали двигать руками
// функция отменяет следование карты за курсором, и устанавливает таймер, чтобы вернуть
// пытается отделить собственные движения карты от юзерских, включая изменение масштаба
if(userMoveMap) { // Убран флаг в куске, двигающем карту за курсором
//alert('Карту сдвинули событием '+event.type);
if(event.type == 'zoomstart') userMoveMap = 2; // юзер нажал zoom
else {
if(userMoveMap == 2) userMoveMap = true; // на это дело сработало movestart - игнорируем
else {
followToCursor=false; // запретим следование за курсором
clearTimeout(followPaused); // отменим то, что есть
followPaused = setTimeout('followToCursor=true;',followPause); // через время followPause разрешим обратно
}
}
}
});
map.on('zoomend', function(event) {
if(distCirclesSwitch.checked) distCirclesUpdate(distCircles); // нарисуем круги дистанции
if(map.hasLayer(centerMark)) centerMarkUpdate(); // нарисуем круги дистанции крестика в центре
});
map.on('moveend', function(event) {
// кластеризация точек POI, показывает кластеры в области просмотра
let zoom = map.getZoom();
let pos = map.getCenter();
// Если не было зумирования и центр сдвинулся мало - не будем перепоказывать supercluster
if((zoom == lastSuperClusterUpdatePosition[1]) && (map.distance(pos,lastSuperClusterUpdatePosition[0]) <= superclusterRadius*(40075016.686 * Math.abs(Math.cos(pos.lat*(Math.PI/180))))/Math.pow(2, zoom+8))) return;
updateClasters();
lastSuperClusterUpdatePosition[0] = pos;
lastSuperClusterUpdatePosition[1] = zoom;
});
//map.on("layeradd", function(event) {
//});
// Восстановим слои
//var layers = JSON.parse(getCookie('GaladrielMaps')); // getCookie from galadrielmap.js
var layers = storageHandler.restore('layers'); // storageHandler from galadrielmap.js
// Занесём слои на карту
if(layers) layers.reverse().forEach(function(layerid){ // потому что они там были для красоты последним слоем вверх
for (var i = 0; i < mapList.children.length; i++) { // для каждого потомка списка mapList
if (mapList.children[i].id==layerid) { //
selectMap(mapList.children[i]);
break;
}
}
});
else {
for (var i = 0; i < mapList.children.length; i++) { // для каждого потомка списка mapList
if (mapList.children[i].id==defaultMap) { // найдём, который из них defaultMap
selectMap(mapList.children[i]); // и покажкм его
break;
}
}
}
// Рисование маршрута
dravingLines.addTo(map);
doRestoreMeasuredPaths(); // восстановим из кук сохранённые на устройстве маршруты
routeControlsDeSelect(); // сделать кнопки рисования невыбранными
var pointIcon = L.icon({
iconUrl: 'leaflet-omnivorePATCHED/symbols/point.png',
iconSize: [32, 37],
iconAnchor: [16, 37],
tooltipAnchor: [16,-25],
className: 'wpIcon',
});
var anchorIcon = L.icon({
iconUrl: 'leaflet-omnivorePATCHED/symbols/anchor.png',
iconSize: [32, 37],
iconAnchor: [16, 37],
tooltipAnchor: [16,-25],
className: 'wpIcon'
});
var cautionIcon = L.icon({
iconUrl: 'leaflet-omnivorePATCHED/symbols/caution.png',
iconSize: [32, 37],
iconAnchor: [16, 37],
tooltipAnchor: [16,-25],
className: 'wpIcon'
});
/*
map.on('editable:editing', // обязательный обработчик для editable для перересовывания расстояний при изменении пути
function (e) {
//console.log('обязательный обработчик для editable start by editable:editing',e);
// А это норм, что оно глобально?
if (e.layer instanceof L.Path) e.layer.updateMeasurements();
}
);
*/
map.on('editable:drawing:end', function(event) {
// выключать кнопку "Начать" при окончании рисования, сделать доступной "Продолжить"
//console.log('map.on [editable:drawing:end] event.target:',event.target);
/*
if(event.layer instanceof L.Marker){
console.log('[map.on editable:drawing:end] event.layer is a L.marker');
}
*/
if(event.layer instanceof L.Path){
//console.log('[map.on editable:drawing:end] event.layer is a L.Path');
routeContinueButton.disabled=false;
}
routeCreateButton.checked=false;
});
map.on('editable:vertex:dragstart', function(event) {
// Придурки из Mozilla сделали Error при вызове этой функции из десктопного браузера, когда как
// в https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vibrate ясно сказано:
// "If the device doesn't support vibration, this method has no effect. "
// И до какого-то момента так всё и было.
// Короче, они это отключили, но никому не сказали: https://github.com/mdn/content/issues/34703
if(typeof window.navigator.vibrate === 'function') window.navigator.vibrate(200); // Вибрировать 200ms
});
// Круги дистанции
var centerMarkCircles = [];
for (let n=0; n<4; n++) {
centerMarkCircles.push( L.circle([], {
color: '#FD00DB',
weight: 1,
opacity: 0.3,
fill: false,
pane: 'overlayPane',
zIndexOffset: -503
}));
distCircles.push(L.circle([0,0], { // указать координаты необходимо, потому что Leaflet обламывается при добавлении круга в мультислой, если у круга нет координат
color: '#FD00DB',
weight: 1,
opacity: 0.3,
fill: false,
pane: 'overlayPane',
zIndexOffset: -503
}));
};
// центр экрана
let centerMarkIcon = new L.divIcon({
className: "centerMarkIcon" // galadrielmap.css Установить прозрачность фона иначе, чем внешним стилем не удаётся
});
var centerMarkMarker = L.marker(map.getBounds().getCenter(), {
'icon': centerMarkIcon,
pane: 'overlayPane', // расположим маркер над тайлами, но ниже всего остального
zIndexOffset: -1000
});
var centerMark = L.layerGroup([centerMarkMarker]);
centerMarkCircles.forEach(circle => circle.addTo(centerMark));
// Символ ветра
let windSymbolIcon = L.divIcon({
className: "", // если не указать className, то для L.divIcon будет рисоваться какой-то квадратик, и от него никак не избавиться. Глюк?
iconAnchor: [-30,0],
html:`
<svg version="1.1" id="wSVGimage"
width="135" height="30"
transform="scale(1,1)"
xmlns="http://www.w3.org/2000/svg">
<defs>
<line id="bLine" x1="0" y1="2.5" x2="70" y2="2.5" />
<line id="w2.5" x1="3" y1="2.5" x2="10" y2="13" />
<polyline id="w5" points="0,2.5 10,2.5 25,25" fill="none"/>
<g id="w25">
<polygon points="10,5 22.5,25 34.5,5" stroke-width="0" />
<line x1="0" y1="2.5" x2="34.5" y2="2.5" />
</g>
</defs>
<g id="wMark" fill="#8900FF" fill-opacity="0.75" stroke="#8900FF" stroke-width="5" stroke-opacity="0.75" >
</g>
</svg>
`
});
let windSymbolMarker = L.marker([],{
icon: windSymbolIcon,
pane: 'overlayPane', // расположим маркер над тайлами, но ниже всего остального
zIndexOffset: -400
});
// Местоположение
// маркеры
var GpsCursor = L.icon({
iconUrl: './img/gpscursor.png',
iconSize: [120, 120], // size of the icon
iconAnchor: [60, 60], // point of the icon which will correspond to marker's location
});
// курсор
var NoGpsCursor = L.icon({ // этот значёк может показываться и при пропаже связи с сервером, а в этом случае загрузить картинку не удастся. Попытка загрузить её заранее не получилась: Leaflet, видимо, убивает долго неиспользуемые объекты. Или сборщик мусора?
iconUrl: './img/gpscursor.png',
iconSize: [120, 120], // size of the icon
iconAnchor: [60, 60], // point of the icon which will correspond to marker's location
className: "NoGpsCursorIcon" // galadrielmap.css
});
var velocityCursor = L.icon({
iconUrl: './img/1x1.png',
//iconUrl: './img/minLine.svg',
});
var NoCursor = L.icon({
iconUrl: './img/1x1.png',
iconSize: [0, 0], // size of the icon
});
var cursor = L.marker(startCenter, {
icon: GpsCursor,
rotationAngle: 0, // начальный угол поворота маркера
rotationOrigin: "50% 50%", // вертим маркер вокруг центра
pane: 'overlayPane', // расположим маркер над тайлами, но ниже всего остального
zIndexOffset: -500
});
// указатель скорости
var velocityVector = L.marker(cursor.getLatLng(), {
'icon': velocityCursor,
rotationAngle: 0, // начальный угол поворота маркера
opacity: 0.1,
pane: 'overlayPane', // расположим маркер над тайлами, но ниже всего остального
zIndexOffset: -501
});
velocityVectorLengthInMnDisplay.innerHTML = velocityVectorLengthInMn; // нарисуем цену вектора скорости на панели управления
// Точность ГПС
var GNSScircle = L.circle(cursor.getLatLng(), {
'radius': 10,
'color':'#000000',
'weight':0,
'opacity':0.1,
'fillOpacity':0.1,
pane: 'overlayPane', // расположим маркер над тайлами, но ниже всего остального
zIndexOffset: -502
});
// Курсор: объединение всех фигур
var positionCursor = L.layerGroup([GNSScircle,velocityVector,cursor]);
distCirclesToggler(); // (если) добавим круги в курсор и заодно освежим куку
windSwitchToggler(); // (если) добавим символ ветра в курсор и заодно освежим куку
// Для визуализации collisionDetector
var collisionIcon = L.icon({
iconUrl: './img/redbulletdot.svg',
iconSize: [60, 60],
iconAnchor: [30, 30]
});
var collisionDirectionIcon = L.icon({
iconUrl: './img/redArrow.svg',
iconSize: [20,24],
iconAnchor: [10,30]
});
var collisisonDetected = L.layerGroup(); // слой, на котором рисуются значки возможных столкновений collisionDetector
var collisionDirectionsCursor = L.layerGroup(); // слой с указателями направлений на опасности столкновений
/////////////////////////// collisionDetector test ///////////////////////////////
//var collisisonAreas = L.layerGroup(); // для тестовых целей collisionDetector
/////////////////////////// end collisionDetector test ///////////////////////////////
// MOB marker
var mobIcon = L.icon({ //
iconUrl: mob_markerImg, // options.js
//iconUrl: "img/mob.png",
iconSize: [32, 37],
//iconSize: [64, 74],
iconAnchor: [16, 37],
//iconAnchor: [32, 74],
tooltipAnchor: [16,-25],
className: 'mobIcon'
});
// линия между положением и указанным маркером MOB
var toMOBline = L.polyline([], {
color: 'red',
weight: 10,
opacity:0.3,
})
// восстановим маркеры
//var mobMarker = getCookie('GaladrielMapMOB'); // getCookie from galadrielmap.js
var mobMarker = storageHandler.restore('mobMarker'); // storageHandler from galadrielmap.js
//console.log('Восстановление MOB: mobMarker',mobMarker);
if(mobMarker) {
let mobMarkerJSON;
try{
mobMarkerJSON = JSON.parse(mobMarker);
}
catch(err){
console.log('saved MOB is bad, JSON.parse error:',err.text);
};
//console.log('from cookie:',JSON.stringify(mobMarkerJSON)); // L.geoJSON не сохраняет левые поля из geoJSON
if((typeof mobMarkerJSON === "object") && mobMarkerJSON.features && (typeof mobMarkerJSON.features === "object")){
let pt = false
for(let feature of mobMarkerJSON.features){
if(feature.geometry.type != 'Point') continue;
if((typeof feature.geometry.coordinates === "object") && feature.geometry.coordinates.length != 0){
pt=true;
break;
};
};
if(pt){ // В куке - объект geojson с точкой с координатами
createMOBpointMarker(mobMarkerJSON);// Восстановим мультислой маркеров из GeoJSON
//console.log('mobMarker from cookie:',mobMarker);
};
};
};
// Если маркера MOB таки нет - создадим его.
if((typeof mobMarker !== "object") || !(mobMarker instanceof L.LayerGroup)) {
mobMarker = L.layerGroup().addLayer(toMOBline); // таким образом, mobMarker всегда есть.
mobMarker.feature = {properties: {}};
};
// Позиционирование
// Realtime периодическое обновление
var spatialWebSocket; //
var TPVdata = {}; // буфер с данными позиционирования, скорости и направления
var lastDataUpdate=0; // момент последнего получения данных, в милисекундах
var PosFreshBeforeMultiplexor=30; // через сколько интервалов PosFreshBefore убирать курсор совсем
var lastPositionUpdate=0; // момент последнего обновления координат, в милисекундах
function spatialWebSocketStart(){
//
let checkDataFreshInterval; // объект периодического запуска проверки свежести данных
spatialWebSocket = new WebSocket(`ws://${document.location.host}/signalk/v1/stream?subscribe=none`); // подписываться будем отдельно, по событию websocketOnOpen
spatialWebSocket.onopen = function(event) {
console.log("[spatialWebSocket open] Connection established");
// Если сервера не было, то списки могли изменится после его перезапуска
// не уверен, что стоит заморачиваться с перегрузкой всех списков, перепоказом маршрутов и вот этим всем...
//console.log("[spatialWebSocket open] Track list and route list refreshing");
//listPopulate(routeList,routeDirURI,false,restoreDisplayedRoutes); // список маршрутов, асинхронно
//listPopulate(trackList,trackDirURI,true,function(){chkDisplayedList(trackList,trackDisplayed);}); // список путей, показывать текущий, асинхронно
TPVsubscribe.subscribe.forEach(subscribe => subscribe.minPeriod = minWATCHinterval); // signalKsubscribe в options.js
event.target.send(JSON.stringify(TPVsubscribe)); // подписываемся на получение данных
console.log("[spatialWebSocket open] Subscribe sended");
//console.log("[spatialWebSocket open] Subscribed to",TPVsubscribe);
// Хоть какая-то проверка актуальности координат -- за отсутствием чего-то такого в SignalK
if(useSystemTimeouts){ // options.js
// Попытка сделать минимально правильно в этом кривом SignalK
// Хотя, в принципе, нужно пытаться получить .meta.timeout для каждой величины.
// Во-первых, meta.timeout реально вообще нигде нет. По идее, его должен указывать производитель данных, но нет.
// Во-вторых, к этому моменту может вообще не быть navigation, а по подписке не присылается meta
// В-третьих, в доке указано, что должен быть meta.timeout, но если смотреть по аналогии с units, скажем, то оно meta.properties.longitude.timeout Как оно правильно -- неизвестно
let timeout = getSelfPathC('navigation.position.meta.timeout');
if(timeout) PosFreshBefore = timeout*1000;
timeout = getSelfPathC(ConfigDepthProp+'.meta.timeout');
if(timeout) {
SpeedFreshBefore = timeout*1000;
DepthFreshBefore = timeout*1000;
WindFreshBefore = timeout*1000;
}
}
// Попытка переподключения к серверу, если позиция давно не обновлялась
checkDataFreshInterval = setInterval(function (){
//console.log('[checkDataFreshInterval] PosFreshBefore=',PosFreshBefore,'lastDataUpdate=',Date.now()-lastDataUpdate);
if((Date.now()-lastDataUpdate)>PosFreshBefore){
console.log('The latest TPV data was received too long ago, trying to reconnect for checking.');
spatialWebSocket.close(1000,'The latest data was received too long ago');
}
},PosFreshBefore);
windSwitchToggler(); // (если) добавим символ ветра в курсор
}; // end spatialWebSocket.onopen
spatialWebSocket.onmessage = function(event) {
//console.log(`[message] Данные TPV получены с сервера: ${event.data}`);
let data;
try{
data = JSON.parse(event.data);
}
catch(error){
console.log('spatialWebSocket: Parsing inbound data',error.message);
return;
}
//console.log('From SignalK',data);
if(!data.updates){ // ответ на какой-то запрос
//console.log('Other From SignalK',data);
if(data.self){ // handshaiking
//vesselSelf = data.self.substring(8); // отрезано vessels.
vesselSelf = data.self;
}
return;
}
for(let update of data.updates){
//console.log('update:',update);
if(!update.values) continue; // непонятно, почему может не быть values, но иногда их нет
for(let value of update.values){ // какое-то обновление данных пришло.
//console.log('[spatialWebSocket.onmessage] recieved value:',value);
lastDataUpdate = Date.now();
switch(value.path){
case 'navigation.position':
TPVdata.lon = value.value.longitude;
TPVdata.lat = value.value.latitude;
//console.log('TPVdata lon lat',TPVdata.lon,TPVdata.lat);
// timestamp будет один на все величины. Это неправильно, но, насколько я понимаю,
// SignalK по крайней мере, для величин, не имеющих своего timestamp в источнике,
// в качестве timestamp ставит текущее время.
TPVdata.positionTime = update.timestamp; // там не требуется unix timestamp, а, наоборот, дата в виде строки
break;
case 'navigation.courseOverGroundTrue':
TPVdata.track = value.value*180/Math.PI;
break;
case 'navigation.headingTrue':
TPVdata.heading = value.value*180/Math.PI;
TPVdata.headingTime = update.timestamp;
//console.log('[spatialWebSocket.onmessage] headingTrue=',TPVdata.heading)
break;
// в принципе, может быть и navigation.headingMagnetic и navigation.headingCompass
// причём разные (с учётом navigation.magneticDeviation)
// Кто им доктор?
case 'navigation.headingMagnetic':
TPVdata.mheading = value.value*180/Math.PI;
TPVdata.mheadingTime = update.timestamp;
case 'navigation.headingCompass':
TPVdata.mhe