Monitor Map (Camera Map)

axis-lock
Moe 2023-07-10 05:08:39 +00:00
parent fa8804a30d
commit 0abae0d95b
12 changed files with 591 additions and 60 deletions

View File

@ -140,7 +140,34 @@ module.exports = function(s,config,lang){
"value": "1"
}
]
}
},
{
"name": "detail=geolocation",
"field": lang["Geolocation"],
"example": "49.2578298,-123.2634732",
"description": lang["fieldTextGeolocation"],
},
{
"id": "monitor-settings-monitor-map-container",
"style": "position: relative",
"fieldType": "div",
divContent: `
<div id="monitor-settings-geolocation-options" class="p-2" style="background: rgba(0,0,0,0.4);position: absolute; top: 0; right: 0; border-radius: 0 0 0 15px; z-index: 405;">
<label for="direction">${lang.Direction} <span class="badge" map-option-value="direction"></span></label>
<div class="slider-container">
<input type="range" map-option="direction" class="slider" min="0" max="360" value="90">
</div>
<label for="fov">${lang['Field of View']} <span class="badge"><span map-option-value="fov"></span>°</span></label>
<div class="slider-container">
<input type="range" map-option="fov" class="slider" min="0" max="180" value="60">
</div>
<label for="range">${lang.Range} <span class="badge"><span map-option-value="range"></span> km</span></label>
<div class="slider-container">
<input type="range" map-option="range" class="slider" min="0" max="10" value="1" step="0.1">
</div>
</div>
<div id="monitor-settings-monitor-map" style="width: 100%;height: 300px;border-radius:15px;"></div>`,
},
]
},
"Connection": {
@ -7718,7 +7745,7 @@ module.exports = function(s,config,lang){
]
},
{
icon: 'map-marker',
icon: 'barcode',
label: `${lang['Power Viewer']}`,
pageOpen: 'powerVideo',
},
@ -7741,6 +7768,11 @@ module.exports = function(s,config,lang){
},
]
},
{
icon: 'map-marker',
label: `${lang['Monitor Map']}`,
pageOpen: 'monitorMap',
},
{
icon: 'file-o',
label: `${lang['FileBin']}`,
@ -8877,146 +8909,130 @@ module.exports = function(s,config,lang){
"box-wrapper-class": "row",
"info": [
{
title: "New to Shinobi?",
title: lang["New to Shinobi?"],
info: `Try reading over some of these links to get yourself started.`,
buttons: [
{
icon: 'newspaper-o',
color: 'default',
text: 'After Installation Guides',
href: 'https://shinobi.video/docs/configure',
class: ''
text: lang.afterInstallationGuides,
href: 'https://shinobi.video/docs/configure'
},
{
icon: 'plus',
color: 'default',
text: 'Adding an H.264 Camera',
href: 'https://shinobi.video/docs/configure#content-adding-an-h264h265-camera',
class: ''
text: lang.addingAnH264Camera,
href: 'https://shinobi.video/docs/configure#content-adding-an-h264h265-camera'
},
{
icon: 'plus',
color: 'default',
text: 'Adding an MJPEG Camera',
href: 'https://shinobi.video/articles/2018-09-19-how-to-add-an-mjpeg-camera',
class: ''
text: lang.addingAnMJPEGCamera,
href: 'https://shinobi.video/articles/2018-09-19-how-to-add-an-mjpeg-camera'
},
{
icon: 'gears',
color: 'default',
text: 'RTSP Camera Optimization',
href: 'https://shinobi.video/articles/2017-07-29-how-i-optimized-my-rtsp-camera',
class: ''
text: lang.rtspCameraOptimization,
href: 'https://shinobi.video/articles/2017-07-29-how-i-optimized-my-rtsp-camera'
},
{
icon: 'comments-o',
color: 'info',
text: 'Community Chat',
href: 'https://discord.gg/ehRd8Zz',
class: ''
text: lang.communityChat,
href: 'https://discord.gg/ehRd8Zz'
},
{
icon: 'reddit',
color: 'info',
text: 'Forum on Reddit',
href: 'https://www.reddit.com/r/ShinobiCCTV',
class: ''
text: lang.forumOnReddit,
href: 'https://www.reddit.com/r/ShinobiCCTV'
},
{
icon: 'file-o',
color: 'primary',
text: 'Documentation',
href: 'http://shinobi.video/docs',
class: ''
text: lang.Documentation,
href: 'http://shinobi.video/docs'
}
]
},
{
bigIcon: "smile-o",
title: "It's a proven fact",
info: `Generosity makes you a happier person, please consider supporting the development.`,
title: lang.itsAProvenFact,
info: lang.generosityHappierPerson,
buttons: [
{
icon: 'share-square-o',
color: 'default',
text: 'ShinobiShop Subscriptions',
href: 'https://licenses.shinobi.video/subscribe',
class: ''
text: lang['ShinobiShop Subscriptions'],
href: 'https://licenses.shinobi.video/subscribe'
},
{
icon: 'paypal',
color: 'default',
text: 'Donate by PayPal',
href: 'https://www.paypal.me/ShinobiCCTV',
class: ''
text: lang['Donate by PayPal'],
href: 'https://www.paypal.me/ShinobiCCTV'
},
{
icon: 'bank',
color: 'default',
text: 'University of Zurich (UZH)',
href: 'https://www.zora.uzh.ch/id/eprint/139275/',
class: ''
href: 'https://www.zora.uzh.ch/id/eprint/139275/'
},
]
},
{
title: "Shinobi Mobile",
info: `Your subscription key can unlock features for <a href="https://cdn.shinobi.video/installers/ShinobiMobile/" target="_blank"><b>Shinobi Mobile</b></a> running on iOS and Android today!`,
title: lang["Shinobi Mobile"],
info: lang.yourSubscriptionText,
buttons: [
{
icon: 'star',
color: 'success',
text: 'Join Public Beta',
href: 'https://shinobi.video/mobile',
class: ''
text: lang['Get the Mobile App'],
href: 'https://shinobi.video/mobile'
},
{
icon: 'comments-o',
color: 'primary',
text: '<b>#mobile-client</b> Chat',
href: 'https://discord.gg/ehRd8Zz',
class: ''
text: lang['#mobile-client Chat'],
href: 'https://discord.gg/ehRd8Zz'
},
]
},
{
title: "Support the Development",
info: `Subscribe to any of the following to boost development! Once subscribed put your Subscription ID in at the Super user panel, then restart Shinobi to Activate your installation, thanks! <i class="fa fa-smile-o"></i>`,
title: lang.activateShinobi,
info: lang.howToActivate,
buttons: [
{
icon: 'share-square-o',
color: 'default',
text: 'Shinobi Mobile License ($5/m)',
href: 'https://licenses.shinobi.video/subscribe?planSubscribe=plan_G31AZ9mknNCa6z',
class: ''
},
{
icon: 'share-square-o',
color: 'default',
text: 'Tiny Support Subscription ($10/m)',
href: 'https://licenses.shinobi.video/subscribe?planSubscribe=plan_G42jNgIqXaWmIC',
class: ''
},
{
icon: 'share-square-o',
color: 'default',
text: 'Shinobi Pro License ($75/m)',
text: '100 Camera License ($75/m)',
href: 'https://licenses.shinobi.video/subscribe?planSubscribe=plan_G3LGdNwA8lSmQy',
class: ''
},
]
},
{
title: "Donations, One-Time Boost",
info: `Sometimes a subscription isn't practical for people. In which case you may show support through a PayPal donation. And as a thank you for doing so your <b>PayPal Transaction ID</b> can be used as a <code>subscriptionId</code> in your Shinobi configuration file. <br><br>Each 5 USD/EUR or 7 CAD will provide one month of activated usage. <i>Meaning, a $20 USD donation today makes this popup go away (or activates the mobile app) for 4 months.</i>`,
title: lang['Donations, One-Time Boost'],
info: lang.donationOneTimeText,
width: 12,
buttons: [
{
icon: 'paypal',
color: 'default',
text: 'Donate by PayPal',
href: 'https://www.paypal.me/ShinobiCCTV',
class: ''
text: lang['Donate by PayPal'],
href: 'https://www.paypal.me/ShinobiCCTV'
},
]
},
@ -9051,5 +9067,22 @@ module.exports = function(s,config,lang){
},
}
},
"Monitor Map": {
"section": "Monitor Map",
"blocks": {
"Search Settings": {
"name": lang["Monitor Map"],
"color": "blue",
"noHeader": true,
"noDefaultSectionClasses": true,
"info": [
{
"fieldType": "div",
"id": "monitor-map-canvas",
}
]
},
}
},
})
}

View File

@ -14,6 +14,9 @@
"monitorDeleted": "Monitor Deleted",
"accountCreationError": "Account Creation Error",
"accountEditError": "Account Edit Error",
"Monitor Map": "Monitor Map",
"Geolocation": "Geolocation",
"fieldTextGeolocation": "The map coordinates of this camera in the real world. This will plot a point for your camera on the Monitor Map.",
"Unmute": "Unmute",
"byUser": "by user",
"accountDeleted": "Account Deleted",
@ -371,9 +374,20 @@
"Execute Command": "Execute Command",
"for Global Access": "for Global Access",
"Help": "Help",
"Range": "Range",
"Direction": "Direction",
"Field of View": "Field of View",
"Don't show this anymore": "Don't show this anymore",
"New to Shinobi?": "New to Shinobi?",
"Get the Mobile App": "Get the Mobile App",
"#mobile-client Chat": "#mobile-client Chat",
"Donations, One-Time Boost": "Donations, One-Time Boost",
"donationOneTimeText": "Sometimes a subscription isn't practical for people. In which case you may show support through a PayPal donation. And as a thank you for doing so your <b>PayPal Transaction ID</b> can be used as a <code>subscriptionId</code> in your Shinobi configuration file. <br><br>Each 5 USD/EUR or 7 CAD will provide one month of activated usage. <i>Meaning, a $20 USD donation today makes this popup go away (or activates the mobile app) for 4 months.</i>",
"Chat on Discord": "Chat on Discord",
"Documentation": "Documentation",
"Donate by PayPal": "Donate by PayPal",
"Join Public Beta": "Join Public Beta",
"ShinobiShop Subscriptions": "ShinobiShop Subscriptions",
"All Monitors": "All Monitors",
"Motion Meter": "Motion Meter",
"FFmpegTip": "FFprobe is a simple multimedia streams analyzer. You can use it to output all kinds of information about an input including duration, frame rate, frame size, etc.",
@ -990,6 +1004,7 @@
"WebdavErrorTextCreatingDir": "Cannot create directory.",
"File Not Exist": "File Not Exist",
"No Videos Found": "No Videos Found",
"activateRequiredLiveStream": "Live Stream is only available here with an Activated installation.",
"FileNotExistText": "Cannot save non existant file. Something went wrong.",
"CameraNotRecordingText": "Settings may be incompatible. Check encoders. Restarting...",
"Camera is not running": "Camera is not running",
@ -1786,6 +1801,17 @@
"fieldTextChannelStreamScaleY": "Height of the stream image that is output after processing.",
"fieldTextChannelStreamRotate": "Change the viewing angle of the video stream.",
"fieldTextChannelSvf": "Place FFMPEG video filters in this box to affect the streaming portion. No spaces.",
"afterInstallationGuides": "After Installation Guides",
"addingAnH264Camera": "Adding an H.264 Camera",
"addingAnMJPEGCamera": "Adding an MJPEG Camera",
"rtspCameraOptimization": "RTSP Camera Optimization",
"communityChat": "Community Chat",
"forumOnReddit": "Forum on Reddit",
"forumOnReddit": "Forum on Reddit",
"activateShinobi": "Activate Shinobi",
"howToActivate": "Subscribe to any of the following to support the development and activate your Shinobi! Once subscribed put your Subscription ID in at the Super user panel, then restart Shinobi to Activate your installation, thanks! <i class=\"fa fa-smile-o\"></i>",
"Shinobi Mobile": "Shinobi Mobile",
"yourSubscriptionText": "Your subscription key can unlock features for <a href='https://cdn.shinobi.video/installers/ShinobiMobile/' target='_blank'><b>Shinobi Mobile</b></a> running on iOS and Android today!",
"Last Updated": "Last Updated",
"Z-Wave Manager": "Z-Wave Manager",
"Z-Wave": "Z-Wave",
@ -1798,5 +1824,7 @@
"deleteFace": "Delete Face",
"deleteFaceText": "Are you sure you want to delete ALL the images for this face? they will not be recoverable.",
"deleteImage": "Delete Image",
"deleteImageText": "Are you sure you want to delete this image? it will not be recoverable."
"deleteImageText": "Are you sure you want to delete this image? it will not be recoverable.",
"itsAProvenFact": "It's a proven fact",
"generosityHappierPerson": "Generosity makes you a happier person, please consider supporting the development."
}

View File

@ -82,6 +82,7 @@ module.exports = function(s,config,lang,io){
'home/fileBin',
'home/videosTable',
'home/studio',
'home/monitorMap',
'confirm',
'home/help',
]

View File

@ -0,0 +1,37 @@
#monitor-map-canvas {
height: calc(100vh - 2rem);
border-radius: 20px;
}
#monitor-map-canvas .leaflet-map-pane {
transition: initial;
}
#monitor-map-canvas .leaflet-popup-content {
margin: 0px;
width: 400px!important;
}
#monitor-map-canvas .leaflet-popup-content-wrapper {
padding: 0px;
overflow: hidden;
}
#monitor-map-canvas iframe {
width: 100%;
height: 300px
}
#leaflet-monitor-block {
width:100%;
}
#leaflet-monitor-videos {
overflow:auto;
height: 250px;
padding: 0.4rem 0.5rem;
}
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
background-color: #121417;
}
.leaflet-container a.btn {
color: #fff;
}
.leaflet-marker-icon {
transition: 0s;
}

View File

@ -0,0 +1,156 @@
$(document).ready(function(){
var theBlock = $('#tab-monitorMap')
var theMap = $('#monitor-map-canvas')
var loadedMap;
function loadPopupVideoList(monitor){
var groupKey = monitor.ke
var monitorId = monitor.mid
getVideos({
monitorId: monitorId,
limit: 10,
},function(data){
var videos = data.videos
var html = ''
setTimeout(function(){
var theVideoList = $(`#leaflet-monitor-videos`)
if(videos.length > 0){
theVideoList.css('height','')
console.log(2,videos,videos.length)
$.each(videos,function(n,video){
html += createVideoRow(video,`col-12 mb-2`)
})
}else{
theVideoList.css('height','initial')
html = `<div class="text-center text-light mb-2">${lang['No Videos Found']}</div>`
}
theVideoList.html(html)
},1000)
})
}
function buildPinPopupHtml(monitor){
var embedUrl = buildEmbedUrl(monitor)
var html = `
<div id="leaflet-monitor-block" data-mid="${monitor.mid}" data-ke="${monitor.ke}">
<div>${userHasSubscribed ? `<iframe src="${embedUrl}"></iframe>` : `<div class="text-center p-3 text-light cursor-pointer" onclick="openTab('helpWindow')">${lang.activateRequiredLiveStream}</div>`}</div>
<div id="leaflet-monitor-videos">
<div class="text-center text-light" style="padding-top: 75px"><i class="fa fa-3x fa-spinner fa-pulse"></i></div>
</div>
</div>
`
return html
}
function getPinsFromMonitors(){
var points = []
var n = 0;
$.each(loadedMonitors,function(monitorId,monitor){
var geolocation = monitor.details.geolocation
var modeIsOn = monitor.mode === 'record' || monitor.mode === 'start'
var point = {
coords: [49.2578298 + n,-123.2634732 + n],
direction: 90,
fov: 60,
range: 1,
title: `${monitor.name} (${monitor.host})`,
html: buildPinPopupHtml(monitor)
};
if(!modeIsOn){
}else if(geolocation){
var {
lat,
lng,
zoom,
direction,
fov,
range,
} = getGeolocationParts(monitor.details.geolocation);
point.direction = direction
point.fov = fov
point.range = range
point.coords = [lat, lng]
console.log(point)
points.push(point)
}else{
n += 0.0001105;
points.push(point)
}
})
return points
}
function plotPinsToMap(pins){
for (var i = 0; i < pins.length; i++) {
var pin = pins[i];
var lat = pin.coords[0];
var lng = pin.coords[1];
var html = pin.html;
L.marker([lat, lng], { title: pin.title }).bindPopup(html).addTo(loadedMap);
console.log(pin)
drawMapMarkerFov(loadedMap,{
lat,
lng,
direction: pin.direction,
fov: pin.fov,
range: pin.range,
});
}
}
function loadMap(){
console.log('Load Map')
var monitorMapInfo = dashboardOptions().monitorMap || {
center: { lat:49.2578298, lng:-123.2634732 },
zoom: 13
};
var center = monitorMapInfo.center
var lat = center.lat
var lng = center.lng
var zoom = monitorMapInfo.zoom
var monitorPins = getPinsFromMonitors()
loadedMap = L.map('monitor-map-canvas').setView([lat, lng], zoom);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
}).addTo(loadedMap);
loadedMap.on('moveend', function() {
saveCurrentPosition()
});
loadedMap.on('popupopen', function(e) {
var popup = $('#leaflet-monitor-block')
var groupKey = popup.attr('data-ke')
var monitorId = popup.attr('data-mid')
var monitor = loadedMonitors[monitorId]
console.log(`loading `,monitorId,!!monitor)
loadPopupVideoList(monitor)
});
plotPinsToMap(monitorPins)
}
function unloadMap(){
loadedMap.remove();
loadedMap = null;
}
function saveCurrentPosition(){
var center = loadedMap.getCenter();
var zoom = loadedMap.getZoom();
dashboardOptions('monitorMap',{
center,
zoom,
})
}
addOnTabOpen('monitorMap', function () {
loadMap()
})
addOnTabReopen('monitorMap', function () {
loadMap()
})
addOnTabAway('monitorMap', function () {
unloadMap()
})
onInitWebsocket(function (d){
})
onWebSocketEvent(function (d){
switch(d.f){
case'monitor_edit':
break;
case'monitor_status':
break;
}
})
})

View File

@ -0,0 +1,89 @@
function calculateFOV(camera, direction, fieldOfView, range) {
var startAngle = (direction - fieldOfView / 2) * Math.PI / 180;
var endAngle = (direction + fieldOfView / 2) * Math.PI / 180;
var sectorPoints = [camera];
for (var angle = startAngle; angle <= endAngle; angle += Math.PI / 180) {
var dx = range * Math.cos(angle);
var dy = range * Math.sin(angle);
var lat = camera[0] + dy / 111.325; // Rough conversion from kilometers to degrees
var lng = camera[1] + dx / (111.325 * Math.cos(camera[0] * Math.PI / 180)); // Rough conversion from kilometers to degrees
sectorPoints.push([lat, lng]);
}
sectorPoints.push(camera);
return sectorPoints;
}
function drawMapMarkerFov(map, {
lat,
lng,
direction,
fov,
range
}){
var fovEl = L.polygon(calculateFOV([lat, lng], direction, fov, range), {color: 'red'}).addTo(map);
return fovEl
}
function setMapMarkerFov(fovEl,{
lat,
lng,
direction,
fov,
range,
}){
fovEl.setLatLngs(calculateFOV([lat, lng], direction, fov, range));
}
function getGeolocationParts(geolocation){
var defaultLat = 49.2578298
var defaultLng = -123.2634732
var defaultZoom = 13
var defaultDirection = 90
var defaultFov = 60
var defaultRange = 1
try{
var parts = geolocation.split(',')
var lat = !parts[0] ? defaultLat : parseFloat(parts[0].trim().replace('@','')) || defaultLat
var lng = !parts[1] ? defaultLng : parseFloat(parts[1].trim()) || defaultLng
var zoom = !parts[2] ? defaultZoom : parseFloat(parts[2].trim().replace('v','')) || defaultZoom
var direction = !parts[3] ? defaultDirection : parseFloat(parts[3].trim()) || defaultDirection
var fov = !parts[4] ? defaultFov : parseFloat(parts[4].trim()) || defaultFov
var range = !parts[5] ? defaultRange : parseFloat(parts[5].trim()) || defaultRange
}catch(err){
console.error(err)
var lat = defaultLat
var lng = defaultLng
var zoom = defaultZoom
var direction = defaultDirection
var fov = defaultFov
var range = defaultRange
}
return {
lat,
lng,
zoom,
direction,
fov,
range,
}
}
function getCardinalDirection(degree) {
if (degree >= 337.5 || degree < 22.5) {
return 'N';
} else if (degree >= 22.5 && degree < 67.5) {
return 'NE';
} else if (degree >= 67.5 && degree < 112.5) {
return 'E';
} else if (degree >= 112.5 && degree < 157.5) {
return 'SE';
} else if (degree >= 157.5 && degree < 202.5) {
return 'S';
} else if (degree >= 202.5 && degree < 247.5) {
return 'SW';
} else if (degree >= 247.5 && degree < 292.5) {
return 'W';
} else if (degree >= 292.5 && degree < 337.5) {
return 'NW';
} else {
return 'Invalid degree';
}
}

View File

@ -1,4 +1,8 @@
var monitorEditorSelectedMonitor = null
onMonitorSettingsLoadedExtensions = []
function onMonitorSettingsLoaded(theAction){
onMonitorSettingsLoadedExtensions.push(theAction)
}
$(document).ready(function(e){
//Monitor Editor
@ -434,7 +438,8 @@ window.getMonitorEditFormFields = function(){
}
if(monitorConfig.name == ''){errorsFound.push('Monitor Name cannot be blank')}
//edit details
monitorConfig.details = safeJsonParse(monitorConfig.details)
monitorConfig.details = getDetailValues(editorForm)
// monitorConfig.details = safeJsonParse(monitorConfig.details)
monitorConfig.details.substream = getSubStreamChannelFields()
monitorConfig.details.input_map_choices = monitorSectionInputMapsave()
// TODO : Input Maps and Stream Channels (does old way at the moment)
@ -463,12 +468,10 @@ function getAdditionalStreamChannelFields(tempID,channelId){
var fieldInfo = monitorSettingsAdditionalStreamChannelFieldHtml.replaceAll('$[TEMP_ID]',tempID).replaceAll('$[NUMBER]',channelId)
return fieldInfo
}
addOnTabOpen('monitorSettings', function () {
setFieldVisibility()
drawMonitorSettingsSubMenu()
})
addOnTabReopen('monitorSettings', function () {
setFieldVisibility()
drawMonitorSettingsSubMenu()
@ -688,6 +691,9 @@ function importIntoMonitorEditor(options){
monitorsForCopy.find('optgroup').html(tmp)
setFieldVisibility()
drawMonitorSettingsSubMenu()
onMonitorSettingsLoadedExtensions.forEach(function(theAction){
theAction(monitorConfig)
})
}
//parse "Automatic" field in "Input" Section
monitorEditorWindow.on('change','.auto_host_fill input,.auto_host_fill select',function(e){
@ -1014,9 +1020,9 @@ editorForm.find('[name="type"]').change(function(e){
var el = $(this);
if(el.val()==='h264')editorForm.find('[name="protocol"]').val('rtsp').change()
})
editorForm.find('[detail]').change(function(){
onDetailFieldChange(this)
})
// editorForm.find('[detail]').change(function(){
// onDetailFieldChange(this)
// })
editorForm.on('change','[selector]',function(){
var el = $(this);
onSelectorChange(el,editorForm)

View File

@ -0,0 +1,150 @@
$(document).ready(function(e){
var monitorEditorWindow = $('#tab-monitorSettings')
var monitorSettingsMonitorMap = $('#monitor-settings-monitor-map')
var monitorSettingsMonitorMapContainer = $('#monitor-settings-monitor-map-container')
var monitorSettingsMapOptionsEl = $('#monitor-settings-geolocation-options')
var monitorSettingsMapOptionsElOptions = monitorSettingsMapOptionsEl.find('[map-option]')
var editorForm = monitorEditorWindow.find('form')
var loadedMap;
var monitorMapMarker;
var monitorMapMarkerFov;
function setAdditionalControls(options){
options = options || {}
monitorSettingsMapOptionsElOptions.each(function(n,v){
var el = $(v)
var key = el.attr('map-option')
if(options[key])el.val(options[key]);
})
}
function setGeolocationFieldValue(markerDetails) {
editorForm.find(`[detail="geolocation"]`).val(getMapMarkerPosition(markerDetails))
}
function loadMap(monitor, geoString){
try{
unloadMap()
}catch(err){
}
console.log('MAP LOAD!!!',monitor)
var {
lat,
lng,
zoom,
direction,
fov,
range,
} = getGeolocationParts(geoString || monitor.details.geolocation);
loadedMap = L.map('monitor-settings-monitor-map').setView([lat, lng], zoom);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
}).addTo(loadedMap);
monitorMapMarker = L.marker([lat, lng], {
title: monitor ? `${monitor.name} (${monitor.host})` : null,
draggable: true,
}).addTo(loadedMap);
monitorMapMarker.on('dragend', function(){
setGeolocationFieldValue()
});
monitorMapMarker.on('drag', function(){
var markerDetails = getMapMarkerDetails();
setMapMarkerFov(monitorMapMarkerFov,markerDetails)
});
loadedMap.on('zoomend', function(){
setGeolocationFieldValue()
});
setAdditionalControls({
direction,
fov,
range,
})
monitorMapMarkerFov = drawMapMarkerFov(loadedMap,{
lat,
lng,
direction,
fov,
range,
})
setAdditionalControlsUI({
direction,
fov,
range,
})
}
function unloadMap(){
loadedMap.remove();
loadedMap = null;
}
function getMapOptions(){
var options = {}
monitorSettingsMapOptionsElOptions.each(function(n,v){
var el = $(v)
var key = el.attr('map-option')
var value = el.val()
options[key] = parseFloat(value) || value
})
return options
}
function getMapMarkerDetails(){
var pos = monitorMapMarker.getLatLng()
var zoom = loadedMap.getZoom();
var {
direction,
fov,
range,
} = getMapOptions();
return {
lat: pos.lat,
lng: pos.lng,
zoom,
direction,
fov,
range,
}
}
function getMapMarkerPosition(markerDetails){
var {
lat,
lng,
zoom,
direction,
fov,
range,
} = (markerDetails || getMapMarkerDetails());
return `${lat},${lng},${zoom},${direction},${fov},${range}`
}
function setAdditionalControlsUI(markerDetails){
$.each(markerDetails,function(key,value){
var setValue = `${value}`
if(key === 'direction'){
setValue = `${value} (${getCardinalDirection(value)})`
}
monitorSettingsMapOptionsEl.find(`[map-option-value="${key}"]`).text(setValue)
})
}
editorForm.find(`[detail="geolocation"]`).change(function(){
var geoString = $(this).val();
var currentGeoString = monitorEditorSelectedMonitor.details.geolocation
if(!geoString && currentGeoString){
editorForm.find(`[detail="geolocation"]`).val(currentGeoString)
}
loadMap(monitorEditorSelectedMonitor, geoString)
})
addOnTabOpen('monitorSettings', function () {
loadMap(monitorEditorSelectedMonitor)
})
addOnTabReopen('monitorSettings', function () {
loadMap(monitorEditorSelectedMonitor)
})
addOnTabAway('monitorSettings', function () {
unloadMap()
})
onMonitorSettingsLoaded(function(monitorConfig){
loadMap(monitorConfig)
})
monitorSettingsMapOptionsElOptions.on('input',function(){
var markerDetails = getMapMarkerDetails();
setGeolocationFieldValue(markerDetails)
setMapMarkerFov(monitorMapMarkerFov,markerDetails)
setAdditionalControlsUI(markerDetails)
})
})

View File

@ -289,6 +289,12 @@ function buildStreamUrl(monitorId){
return streamURL
}
function buildEmbedUrl(monitor){
var monitorId = monitor.mid;
var streamURL = `${getApiPrefix(`embed`)}/${monitorId}/fullscreen|jquery|gui|relative?host=${location.pathname}`
return streamURL;
}
function getDbColumnsForMonitor(monitor){
var acceptedFields = [
'mid',

View File

@ -1,4 +1,5 @@
<!-- Core JS Files -->
<script src="<%-window.libURL%>assets/vendor/leaflet/leaflet.js"></script>
<script src="<%-window.libURL%>assets/vendor/js/jquery-ui.min.js"></script>
<script src="<%-window.libURL%>assets/vendor/js/pnotify.custom.min.js"></script>
<script src="<%-window.libURL%>assets/vendor/js/socket.io.min.js"></script>

View File

@ -31,6 +31,7 @@
<link rel="stylesheet" href="<%-window.libURL%>assets/vendor/daterangepicker.css">
<link rel="stylesheet" href="<%-window.libURL%>assets/vendor/gridstack.min.css">
<link rel="stylesheet" href="<%-window.libURL%>assets/vendor/jquery-ui.min.css">
<link rel="stylesheet" href="<%-window.libURL%>assets/vendor/leaflet/leaflet.css" />
<link rel="stylesheet" href="<%-window.libURL%>assets/css/clock.css">
<link rel="stylesheet" href="<%-window.libURL%>assets/css/bs5.liveGrid.css">
<link rel="stylesheet" href="<%-window.libURL%>assets/css/bs5.regionEditor.css">

View File

@ -0,0 +1,23 @@
<main class="page-tab pt-3" id="tab-monitorMap">
<form class="dark row">
<%
var drawBlock
var buildOptions
%>
<%
include fieldBuilders.ejs
%>
<%
var pageName = 'Monitor Map';
Object.keys(define[pageName].blocks).forEach(function(blockKey){
var pageLayout = define[pageName].blocks[blockKey]
drawBlock(pageLayout)
})
%>
</form>
<link rel="stylesheet" href="<%-window.libURL%>assets/css/bs5.monitorMap.css" />
<script src="<%-window.libURL%>assets/js/bs5.monitorMap.utils.js"></script>
<script src="<%-window.libURL%>assets/js/bs5.monitorMap.js"></script>
<script src="<%-window.libURL%>assets/js/bs5.monitorSettings.monitorMap.js"></script>
</main>