kopia lustrzana https://github.com/projecthorus/chasemapper
Use websockets to access sondehub/sondehub-amateur chase car data.
rodzic
aa2176e518
commit
8f4a73245f
|
@ -8,4 +8,4 @@
|
||||||
|
|
||||||
# Now using Semantic Versioning (https://semver.org/) MAJOR.MINOR.PATCH
|
# Now using Semantic Versioning (https://semver.org/) MAJOR.MINOR.PATCH
|
||||||
|
|
||||||
__version__ = "1.4.5"
|
__version__ = "1.5.0"
|
||||||
|
|
|
@ -159,22 +159,3 @@ function get_habitat_vehicles(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Show/Hide all vehicles.
|
|
||||||
function show_habitat_vehicles(){
|
|
||||||
var state = document.getElementById("showOtherCars").checked;
|
|
||||||
for (_car in chase_vehicles){
|
|
||||||
// Add to map, if its not already on there.
|
|
||||||
if(state){
|
|
||||||
if(!chase_vehicles[_car].onmap){
|
|
||||||
chase_vehicles[_car].marker.addTo(map);
|
|
||||||
chase_vehicles[_car].onmap = true;
|
|
||||||
}
|
|
||||||
} else{
|
|
||||||
if(chase_vehicles[_car].onmap){
|
|
||||||
chase_vehicles[_car].marker.remove();
|
|
||||||
chase_vehicles[_car].onmap = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
Plik diff jest za duży
Load Diff
|
@ -79,6 +79,7 @@ function serverSettingsUpdate(data){
|
||||||
|
|
||||||
// Update version
|
// Update version
|
||||||
$('#chasemapper_version').html(chase_config.version);
|
$('#chasemapper_version').html(chase_config.version);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clientSettingsUpdate(){
|
function clientSettingsUpdate(){
|
||||||
|
|
|
@ -1,189 +1,227 @@
|
||||||
//
|
//
|
||||||
// Project Horus - Browser-Based Chase Mapper - SondeHub Data Scraping
|
// Project Horus - Browser-Based Chase Mapper - SondeHub Websockets Connection.
|
||||||
//
|
//
|
||||||
// Copyright (C) 2021 Mark Jessop <vk5qi@rfhead.net>
|
// Copyright (C) 2022 Mark Jessop <vk5qi@rfhead.net>
|
||||||
// Released under GNU GPL v3 or later
|
// Released under GNU GPL v3 or later
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
// URL to scrape recent vehicle position data from.
|
function handleSondeHubWebSocketPacket(data){
|
||||||
var sondehub_vehicle_url = "https://api.v2.sondehub.org/listeners/telemetry?duration=1h";
|
// Handle a packet of vehicle / listener telemetry from a SondeHub / SondeHub-Amateur Websockets Connection.
|
||||||
var sondehub_vehicle_url_amateur = "https://api.v2.sondehub.org/amateur/listeners/telemetry?duration=1h";
|
|
||||||
|
|
||||||
|
// Only process frames where the 'mobile' flag is present and is true.
|
||||||
|
if (data.hasOwnProperty('mobile')){
|
||||||
|
if(data['mobile'] == true){
|
||||||
|
// We have found a mobile station!
|
||||||
|
//console.log(data);
|
||||||
|
|
||||||
// Not really sure if this is necessary.
|
// Extract position.
|
||||||
var sondehub_request_running = false;
|
var v_lat = parseFloat(data.uploader_position[0]);
|
||||||
|
var v_lon = parseFloat(data.uploader_position[1]);
|
||||||
|
var v_alt = parseFloat(data.uploader_position[2]);
|
||||||
|
var vcallsign = data.uploader_callsign;
|
||||||
|
|
||||||
|
// If the vehicle is already known to us, then update it position.
|
||||||
|
// Update any existing entries (even if the range is above the threshold)
|
||||||
|
if (chase_vehicles.hasOwnProperty(vcallsign)){
|
||||||
|
//console.log("Updating: " + vcallsign);
|
||||||
|
// Update the position ID.
|
||||||
|
chase_vehicles[vcallsign].position_id = data.ts;
|
||||||
|
|
||||||
var temp_sondehub = null;
|
// Since we don't always get a heading with the vehicle position, calculate it.
|
||||||
|
var old_v_pos = {lat:chase_vehicles[vcallsign].latest_data[0],
|
||||||
|
lon: chase_vehicles[vcallsign].latest_data[1],
|
||||||
|
alt:chase_vehicles[vcallsign].latest_data[2]};
|
||||||
|
var new_v_pos = {lat: v_lat, lon:v_lon, alt:v_alt};
|
||||||
|
chase_vehicles[vcallsign].heading = calculate_lookangles(old_v_pos, new_v_pos).azimuth;
|
||||||
|
|
||||||
function process_sondehub_vehicles(data){
|
// Update the position data.
|
||||||
// Check we have a 'valid' response to process.
|
chase_vehicles[vcallsign].latest_data = [v_lat, v_lon, v_alt];
|
||||||
if (data === null ||
|
|
||||||
data === {}) {
|
|
||||||
sondehub_request_running = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now iterate over all the callsigns in the response.
|
// Update the marker position.
|
||||||
Object.keys(data).forEach(function(vcallsign){
|
chase_vehicles[vcallsign].marker.setLatLng(chase_vehicles[vcallsign].latest_data).update();
|
||||||
// Skip over our own data.
|
|
||||||
if (vcallsign.startsWith(chase_config.habitat_call)){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( (data[vcallsign] === null) || (data[vcallsign] == {})){
|
// Rotate/replace the icon to match the bearing.
|
||||||
// No data. This shouldn't happen, as the DB shouldn't give us empty results.
|
var _car_heading = chase_vehicles[vcallsign].heading - 90.0;
|
||||||
return;
|
if (_car_heading<=90.0){
|
||||||
|
chase_vehicles[vcallsign].marker.setIcon(habitat_car_icons[chase_vehicles[vcallsign].colour]);
|
||||||
|
chase_vehicles[vcallsign].marker.setRotationAngle(_car_heading);
|
||||||
|
}else{
|
||||||
|
// We are travelling West - we need to use the flipped car icon.
|
||||||
|
_car_heading = _car_heading - 180.0;
|
||||||
|
chase_vehicles[vcallsign].marker.setIcon(habitat_car_icons_flipped[chase_vehicles[vcallsign].colour]);
|
||||||
|
chase_vehicles[vcallsign].marker.setRotationAngle(_car_heading);
|
||||||
|
}
|
||||||
|
|
||||||
}else{
|
} else {
|
||||||
// Array of times for each callsign
|
|
||||||
_position_times = Object.keys(data[vcallsign]);
|
|
||||||
// Get last position, which should be the latest.
|
|
||||||
_last_position = data[vcallsign][_position_times[_position_times.length - 1]];
|
|
||||||
|
|
||||||
// Check if the vehicle is marked as a 'mobile' station
|
// Otherwise, we need to decide if we're going to add it or not.
|
||||||
if (_last_position.hasOwnProperty('mobile')){
|
// Determine the vehicle distance from our current position.
|
||||||
if(_last_position['mobile'] == true){
|
var v_pos = {lat: v_lat, lon:v_lon, alt:v_alt};
|
||||||
// We have found a mobile station!
|
if (chase_car_position.marker === "NONE"){
|
||||||
//console.log(_last_position);
|
var my_pos = {lat:chase_config.default_lat, lon:chase_config.default_lon, alt:0};
|
||||||
|
}else{
|
||||||
|
var my_pos = {lat:chase_car_position.latest_data[0], lon:chase_car_position.latest_data[1], alt:chase_car_position.latest_data[2]};
|
||||||
|
}
|
||||||
|
var v_range = calculate_lookangles(my_pos, v_pos).range/1000.0;
|
||||||
|
|
||||||
// Extract position.
|
// If the range is less than the threshold, add it to our list of chase vehicles.
|
||||||
var v_lat = parseFloat(_last_position.uploader_position[0]);
|
if(v_range < vehicle_max_range){
|
||||||
var v_lon = parseFloat(_last_position.uploader_position[1]);
|
//console.log("Adding: " + vcallsign);
|
||||||
var v_alt = parseFloat(_last_position.uploader_position[2]);
|
chase_vehicles[vcallsign] = {};
|
||||||
|
// Initialise a few default values
|
||||||
// If the vehicle is already known to us, then update it position.
|
chase_vehicles[vcallsign].heading = 90;
|
||||||
// Update any existing entries (even if the range is above the threshold)
|
chase_vehicles[vcallsign].latest_data = [v_lat, v_lon, v_alt];
|
||||||
if (chase_vehicles.hasOwnProperty(vcallsign)){
|
chase_vehicles[vcallsign].position_id = data.ts;
|
||||||
|
|
||||||
// Only update if the position ID of this position is newer than that last seen.
|
// Get an index for the car icon. This is incremented for each vehicle,
|
||||||
if (chase_vehicles[vcallsign].position_id !== _last_position.ts){
|
// giving each a different colour.
|
||||||
//console.log("Updating: " + vcallsign);
|
chase_vehicles[vcallsign].colour = car_colour_values[car_colour_idx];
|
||||||
// Update the position ID.
|
car_colour_idx = (car_colour_idx+1)%car_colour_values.length;
|
||||||
chase_vehicles[vcallsign].position_id = _last_position.ts;
|
|
||||||
|
|
||||||
// Since we don't always get a heading with the vehicle position, calculate it.
|
// Create marker
|
||||||
var old_v_pos = {lat:chase_vehicles[vcallsign].latest_data[0],
|
chase_vehicles[vcallsign].marker = L.marker(chase_vehicles[vcallsign].latest_data,
|
||||||
lon: chase_vehicles[vcallsign].latest_data[1],
|
{title:vcallsign,
|
||||||
alt:chase_vehicles[vcallsign].latest_data[2]};
|
icon: habitat_car_icons[chase_vehicles[vcallsign].colour],
|
||||||
var new_v_pos = {lat: v_lat, lon:v_lon, alt:v_alt};
|
rotationOrigin: "center center"});
|
||||||
chase_vehicles[vcallsign].heading = calculate_lookangles(old_v_pos, new_v_pos).azimuth;
|
|
||||||
|
|
||||||
// Update the position data.
|
// Add tooltip, with custom CSS which removes all tooltip borders, and adds a text shadow.
|
||||||
chase_vehicles[vcallsign].latest_data = [v_lat, v_lon, v_alt];
|
chase_vehicles[vcallsign].marker.bindTooltip(vcallsign,
|
||||||
|
{permanent: true,
|
||||||
// Update the marker position.
|
direction: 'center',
|
||||||
chase_vehicles[vcallsign].marker.setLatLng(chase_vehicles[vcallsign].latest_data).update();
|
offset:[0,25],
|
||||||
|
className:'custom_label'}).openTooltip();
|
||||||
// Rotate/replace the icon to match the bearing.
|
if(document.getElementById("showOtherCars").checked){
|
||||||
var _car_heading = chase_vehicles[vcallsign].heading - 90.0;
|
// Add the car to the map if we have the show other cars button checked.
|
||||||
if (_car_heading<=90.0){
|
chase_vehicles[vcallsign].marker.addTo(map);
|
||||||
chase_vehicles[vcallsign].marker.setIcon(habitat_car_icons[chase_vehicles[vcallsign].colour]);
|
|
||||||
chase_vehicles[vcallsign].marker.setRotationAngle(_car_heading);
|
|
||||||
}else{
|
|
||||||
// We are travelling West - we need to use the flipped car icon.
|
|
||||||
_car_heading = _car_heading - 180.0;
|
|
||||||
chase_vehicles[vcallsign].marker.setIcon(habitat_car_icons_flipped[chase_vehicles[vcallsign].colour]);
|
|
||||||
chase_vehicles[vcallsign].marker.setRotationAngle(_car_heading);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No need to go any further.
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, we need to decide if we're going to add it or not.
|
|
||||||
// Determine the vehicle distance from our current position.
|
|
||||||
var v_pos = {lat: v_lat, lon:v_lon, alt:v_alt};
|
|
||||||
if (chase_car_position.marker === "NONE"){
|
|
||||||
var my_pos = {lat:chase_config.default_lat, lon:chase_config.default_lon, alt:0};
|
|
||||||
}else{
|
|
||||||
var my_pos = {lat:chase_car_position.latest_data[0], lon:chase_car_position.latest_data[1], alt:chase_car_position.latest_data[2]};
|
|
||||||
}
|
|
||||||
var v_range = calculate_lookangles(my_pos, v_pos).range/1000.0;
|
|
||||||
|
|
||||||
// If the range is less than the threshold, add it to our list of chase vehicles.
|
|
||||||
if(v_range < vehicle_max_range){
|
|
||||||
//console.log("Adding: " + vcallsign);
|
|
||||||
chase_vehicles[vcallsign] = {};
|
|
||||||
// Initialise a few default values
|
|
||||||
chase_vehicles[vcallsign].heading = 90;
|
|
||||||
chase_vehicles[vcallsign].latest_data = [v_lat, v_lon, v_alt];
|
|
||||||
chase_vehicles[vcallsign].position_id = _last_position.ts;
|
|
||||||
|
|
||||||
// Get an index for the car icon. This is incremented for each vehicle,
|
|
||||||
// giving each a different colour.
|
|
||||||
chase_vehicles[vcallsign].colour = car_colour_values[car_colour_idx];
|
|
||||||
car_colour_idx = (car_colour_idx+1)%car_colour_values.length;
|
|
||||||
|
|
||||||
// Create marker
|
|
||||||
chase_vehicles[vcallsign].marker = L.marker(chase_vehicles[vcallsign].latest_data,
|
|
||||||
{title:vcallsign,
|
|
||||||
icon: habitat_car_icons[chase_vehicles[vcallsign].colour],
|
|
||||||
rotationOrigin: "center center"})
|
|
||||||
.addTo(map);
|
|
||||||
// Keep our own record of if this marker has been added to a map,
|
// Keep our own record of if this marker has been added to a map,
|
||||||
// as we shouldn't be using the private _map property of the marker object.
|
// as we shouldn't be using the private _map property of the marker object.
|
||||||
chase_vehicles[vcallsign].onmap = true;
|
chase_vehicles[vcallsign].onmap = true;
|
||||||
|
|
||||||
// Add tooltip, with custom CSS which removes all tooltip borders, and adds a text shadow.
|
|
||||||
chase_vehicles[vcallsign].marker.bindTooltip(vcallsign,
|
|
||||||
{permanent: true,
|
|
||||||
direction: 'center',
|
|
||||||
offset:[0,25],
|
|
||||||
className:'custom_label'}).openTooltip();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
|
||||||
|
|
||||||
sondehub_request_running = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request the latest vehicle positions from Sondehub
|
|
||||||
function get_sondehub_vehicles(){
|
|
||||||
|
|
||||||
if(!sondehub_request_running){
|
function flush_sondehub_vehicles(){
|
||||||
sondehub_request_running = true;
|
for (_car in chase_vehicles){
|
||||||
console.log("Requesting vehicles from Sondehub...")
|
// Remove from map if present.
|
||||||
$.ajax({
|
if(chase_vehicles[_car].onmap){
|
||||||
url: sondehub_vehicle_url,
|
chase_vehicles[_car].marker.remove();
|
||||||
dataType: 'json',
|
chase_vehicles[_car].onmap = false;
|
||||||
timeout: 15000,
|
}
|
||||||
async: true,
|
delete chase_vehicles[_car];
|
||||||
success: function(data) {
|
|
||||||
console.log(data);
|
|
||||||
process_sondehub_vehicles(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request the latest vehicle positions from Sondehub-Amateur
|
//
|
||||||
function get_sondehub_amateur_vehicles(){
|
// SondeHub Websockets connection.
|
||||||
|
//
|
||||||
|
var livedata = "wss://ws-reader.v2.sondehub.org/";
|
||||||
|
var clientID = "ChaseMapper-" + Math.floor(Math.random() * 10000000000);
|
||||||
|
var client;
|
||||||
|
var clientConnected = false;
|
||||||
|
var clientActive = false;
|
||||||
|
var clientTopic;
|
||||||
|
|
||||||
if(!sondehub_request_running){
|
function onConnect() {
|
||||||
sondehub_request_running = true;
|
if (chase_config.profiles[chase_config.selected_profile].online_tracker === "sondehub") {
|
||||||
console.log("Requesting vehicles from Sondehub Amateur...")
|
var topic = "listener/#";
|
||||||
$.ajax({
|
client.subscribe(topic);
|
||||||
url: sondehub_vehicle_url_amateur,
|
clientTopic = topic;
|
||||||
dataType: 'json',
|
} else if (chase_config.profiles[chase_config.selected_profile].online_tracker === "sondehubamateur") {
|
||||||
timeout: 15000,
|
var topic = "amateur-listener/#";
|
||||||
async: true,
|
client.subscribe(topic);
|
||||||
success: function(data) {
|
clientTopic = topic;
|
||||||
console.log(data);
|
} else {
|
||||||
process_sondehub_vehicles(data);
|
return;
|
||||||
}
|
}
|
||||||
});
|
clientConnected = true;
|
||||||
}
|
clientActive = true;
|
||||||
|
console.log("SondeHub Websockets Connected - Subscribed to " + clientTopic);
|
||||||
|
};
|
||||||
|
|
||||||
|
function connectionError(error) {
|
||||||
|
clientConnected = false;
|
||||||
|
clientActive = false;
|
||||||
|
console.log("SondeHub Websockets Connection Error");
|
||||||
|
};
|
||||||
|
|
||||||
|
function onConnectionLost(responseObject) {
|
||||||
|
if (responseObject.errorCode !== 0) {
|
||||||
|
clientConnected = false;
|
||||||
|
clientActive = false;
|
||||||
|
console.log("SondeHub Websockets Connection Lost");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function onMessageArrived(message) {
|
||||||
|
try {
|
||||||
|
if (clientActive) {
|
||||||
|
var frame = JSON.parse(message.payloadString.toString());
|
||||||
|
handleSondeHubWebSocketPacket(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(err) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
function startSondeHubWebsockets() {
|
||||||
|
if(document.getElementById("showOtherCars").checked){
|
||||||
|
// Clear off any vehicles on the map.
|
||||||
|
flush_sondehub_vehicles();
|
||||||
|
|
||||||
|
if(clientConnected == false){
|
||||||
|
// Not connected yet. Start a new connection.
|
||||||
|
client = new Paho.Client(livedata, clientID);
|
||||||
|
client.onConnectionLost = onConnectionLost;
|
||||||
|
client.onMessageArrived = onMessageArrived;
|
||||||
|
client.connect({onSuccess:onConnect,onFailure:connectionError,reconnect:true});
|
||||||
|
} else {
|
||||||
|
// Already connected, un-sub and re-sub to the correct topic.
|
||||||
|
client.unsubscribe(clientTopic);
|
||||||
|
onConnect();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(clientConnected || (client != null)){
|
||||||
|
client.disconnect();
|
||||||
|
clientConnected = false;
|
||||||
|
console.log("SondeHub Websockets Disconnected.")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Show/Hide all vehicles.
|
||||||
|
function show_sondehub_vehicles(){
|
||||||
|
var state = document.getElementById("showOtherCars").checked;
|
||||||
|
|
||||||
|
for (_car in chase_vehicles){
|
||||||
|
// Add to map, if its not already on there.
|
||||||
|
if(state){
|
||||||
|
if(!chase_vehicles[_car].onmap){
|
||||||
|
chase_vehicles[_car].marker.addTo(map);
|
||||||
|
chase_vehicles[_car].onmap = true;
|
||||||
|
}
|
||||||
|
} else{
|
||||||
|
if(chase_vehicles[_car].onmap){
|
||||||
|
chase_vehicles[_car].marker.remove();
|
||||||
|
chase_vehicles[_car].onmap = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-connect to websockets if necessary.
|
||||||
|
startSondeHubWebsockets();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Habitat ChaseCar lib (copied from SondeHub Tracker)
|
/* Habitat ChaseCar lib (copied from SondeHub Tracker)
|
||||||
* Uploads geolocation for chase cars to habitat
|
* Uploads geolocation for chase cars to habitat
|
||||||
*
|
*
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
<script src="{{ url_for('static', filename='js/predictions.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/predictions.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/car.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/car.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/bearings.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/bearings.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/paho-mqtt.js') }}"></script>
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
|
|
||||||
|
@ -134,6 +134,8 @@
|
||||||
// (i.e. when another client makes a change)
|
// (i.e. when another client makes a change)
|
||||||
socket.on('server_settings_update', function(data){
|
socket.on('server_settings_update', function(data){
|
||||||
serverSettingsUpdate(data);
|
serverSettingsUpdate(data);
|
||||||
|
// Re-Connect to websockets if necessary.
|
||||||
|
startSondeHubWebsockets();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add handlers for various text fields.
|
// Add handlers for various text fields.
|
||||||
|
@ -675,22 +677,8 @@
|
||||||
}
|
}
|
||||||
}, age_update_rate);
|
}, age_update_rate);
|
||||||
|
|
||||||
|
// Start connection to Sondehub Websockets.
|
||||||
// Habitat/Sondehub Chase Car Position Grabber
|
startSondeHubWebsockets();
|
||||||
var habitat_update_rate = 20000;
|
|
||||||
window.setInterval(function(){
|
|
||||||
if(document.getElementById("showOtherCars").checked){
|
|
||||||
if (chase_config.profiles[chase_config.selected_profile].online_tracker === "sondehub"){
|
|
||||||
get_sondehub_vehicles();
|
|
||||||
}
|
|
||||||
else if (chase_config.profiles[chase_config.selected_profile].online_tracker === "sondehubamateur"){
|
|
||||||
get_sondehub_amateur_vehicles();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
// Do nothing...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, habitat_update_rate);
|
|
||||||
|
|
||||||
// Sadly we can't request location information unless chasemapper is served up via HTTPS
|
// Sadly we can't request location information unless chasemapper is served up via HTTPS
|
||||||
// which is a bit difficult to make happen.
|
// which is a bit difficult to make happen.
|
||||||
|
@ -859,7 +847,7 @@
|
||||||
|
|
||||||
<h3>Chase Car Upload</h3>
|
<h3>Chase Car Upload</h3>
|
||||||
<div class="form-switch form-check-reverse">
|
<div class="form-switch form-check-reverse">
|
||||||
<input class="form-check-input" type="checkbox" role="switch" value="" id="showOtherCars" onclick="show_habitat_vehicles();">
|
<input class="form-check-input" type="checkbox" role="switch" value="" id="showOtherCars" onclick="show_sondehub_vehicles();">
|
||||||
<label class="form-check-label" for="showOtherCars">
|
<label class="form-check-label" for="showOtherCars">
|
||||||
<b>Show Nearby Chase-Cars </b>
|
<b>Show Nearby Chase-Cars </b>
|
||||||
</label>
|
</label>
|
||||||
|
|
Ładowanie…
Reference in New Issue