bt-sensors-plugin-sk 1.3.6-2 → 1.3.6-beta4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BTSensor.js +5 -0
- package/README.md +6 -0
- package/classLoader.js +2 -2
- package/package.json +1 -1
- package/plugin_defaults.json +1 -2
- package/sensor_classes/Beacon/AbstractBeaconMixin.js +27 -9
- package/sensor_classes/GobiusCTankMeter.js +15 -0
- package/sensor_classes/JikongBMS.js +0 -1
- package/sensor_classes/Victron/VictronSensor.js +7 -1
- package/src/components/BeaconRenderer.js +261 -0
package/BTSensor.js
CHANGED
|
@@ -975,6 +975,9 @@ class BTSensor extends EventEmitter {
|
|
|
975
975
|
|
|
976
976
|
emit(tag, value){
|
|
977
977
|
super.emit(tag, value)
|
|
978
|
+
if (this.usingGATT()) //update last contact time only for GATT devices
|
|
979
|
+
//which do not receive propertyChanged events when connected
|
|
980
|
+
this._lastContact=Date.now()
|
|
978
981
|
this.setCurrentValue(tag,value)
|
|
979
982
|
}
|
|
980
983
|
|
|
@@ -1091,8 +1094,10 @@ class BTSensor extends EventEmitter {
|
|
|
1091
1094
|
})
|
|
1092
1095
|
}
|
|
1093
1096
|
updatePath(path, val, id, source){
|
|
1097
|
+
|
|
1094
1098
|
this._app.handleMessage(id, {updates: [ { $source: source, values: [ { path: path, value: val }] } ] })
|
|
1095
1099
|
}
|
|
1100
|
+
|
|
1096
1101
|
elapsedTimeSinceLastContact(){
|
|
1097
1102
|
if (this.device instanceof OutOfRangeDevice)
|
|
1098
1103
|
return Infinity
|
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## WHAT'S NEW
|
|
4
4
|
|
|
5
|
+
# Version 1.3.6-beta4
|
|
6
|
+
|
|
7
|
+
- https://github.com/naugehyde/bt-sensors-plugin-sk/issues/107
|
|
8
|
+
- https://github.com/naugehyde/bt-sensors-plugin-sk/issues/106
|
|
9
|
+
- https://github.com/naugehyde/bt-sensors-plugin-sk/issues/103
|
|
10
|
+
|
|
5
11
|
# Version 1.3.6
|
|
6
12
|
|
|
7
13
|
- New sensor parameter: no contact threshhold.
|
package/classLoader.js
CHANGED
|
@@ -15,7 +15,7 @@ const semver = require('semver')
|
|
|
15
15
|
classMap.set(cls.name, cls);
|
|
16
16
|
}
|
|
17
17
|
catch (e) {
|
|
18
|
-
console.log(`Unable to load
|
|
18
|
+
console.log(`Unable to load classfile (${file}): ${e.message}`)
|
|
19
19
|
console.log(e)
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -38,7 +38,7 @@ const semver = require('semver')
|
|
|
38
38
|
const cls = require(module.location+module.module+"/"+classFile);
|
|
39
39
|
classMap.set(cls.name, cls);
|
|
40
40
|
} catch (e) {
|
|
41
|
-
console.log(`Unable to load
|
|
41
|
+
console.log(`Unable to load classfile (${file}): ${e.message}`)
|
|
42
42
|
console.log(e)
|
|
43
43
|
}
|
|
44
44
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bt-sensors-plugin-sk",
|
|
3
|
-
"version": "1.3.6-
|
|
3
|
+
"version": "1.3.6-beta4",
|
|
4
4
|
"description": "Bluetooth Sensors for Signalk - see https://www.npmjs.com/package/bt-sensors-plugin-sk#supported-sensors for a list of supported sensors",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
package/plugin_defaults.json
CHANGED
|
@@ -5,7 +5,7 @@ function getDistances(distanceManager, mac, txPower, rssi ){
|
|
|
5
5
|
const accuracy = (Math.min(1,+(txPower/rssi).toFixed(3)))
|
|
6
6
|
return {
|
|
7
7
|
avgDistance: +distanceManager.getDistance(mac, txPower, DistanceManager.METHOD_AVG, true).toFixed(2),
|
|
8
|
-
|
|
8
|
+
weightedAvgDistance: +distanceManager.getDistance(mac, txPower, DistanceManager.METHOD_WEIGHTED_AVG, true).toFixed(2),
|
|
9
9
|
sampledDistance: +distanceManager.getDistance(mac, txPower, DistanceManager.METHOD_LAST_FEW_SAMPLES, true).toFixed(2),
|
|
10
10
|
accuracy: accuracy
|
|
11
11
|
}
|
|
@@ -20,7 +20,7 @@ class AbstractBeaconMixin {
|
|
|
20
20
|
this.propertiesChanged=this.propertiesChanged.bind(obj)
|
|
21
21
|
this.activate=this.activate.bind(obj)
|
|
22
22
|
this.stopListening=this.stopListening.bind(obj)
|
|
23
|
-
obj.GPSLog=[]
|
|
23
|
+
obj.GPSLog={log:[],beam:0,loa:0}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
initListen() {
|
|
@@ -86,16 +86,17 @@ class AbstractBeaconMixin {
|
|
|
86
86
|
this.emit("approxDistance", distances)
|
|
87
87
|
this.emit("onBoard", distances.avgDistance<this?.presenceThreshold??20)
|
|
88
88
|
}
|
|
89
|
-
if (
|
|
90
|
-
this.GPSLog.unshift(
|
|
89
|
+
if (his.GPSLog.currentPosition){
|
|
90
|
+
this.GPSLog.log.unshift(
|
|
91
91
|
{
|
|
92
92
|
timestamp: new Date().toISOString(),
|
|
93
|
-
|
|
94
|
-
longitude: this._position.longitude,
|
|
93
|
+
heading: this.GPSLog.heading,
|
|
95
94
|
distances: distances
|
|
96
95
|
})
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
Object.assign(this.GPSLog.log[0], this.GPSLog.currentPosition)
|
|
97
|
+
|
|
98
|
+
if (this.GPSLog.log.length>AbstractBeaconMixin.logSize){
|
|
99
|
+
this.GPSLog.log = this.GPSLog.log.slice(0,AbstractBeaconMixin.logSize)
|
|
99
100
|
}
|
|
100
101
|
this.emit("GPSLog", this.GPSLog)
|
|
101
102
|
}
|
|
@@ -104,13 +105,26 @@ class AbstractBeaconMixin {
|
|
|
104
105
|
|
|
105
106
|
async activate(config,paths){
|
|
106
107
|
|
|
108
|
+
this.GPSLog.beam=this._app.getSelfPath('design.beam')
|
|
109
|
+
this.GPSLog.loa=this._app.getSelfPath('design.length.overall')
|
|
110
|
+
|
|
107
111
|
this._positionSub = this._app.streambundle.getSelfStream('navigation.position')
|
|
108
112
|
.onValue(
|
|
109
113
|
(pos) => {
|
|
110
|
-
this.
|
|
114
|
+
this.GPSLog.currentPosition=pos
|
|
115
|
+
|
|
116
|
+
this.emit("GPSLog",this.GPSLog)
|
|
111
117
|
}
|
|
112
118
|
);
|
|
113
119
|
|
|
120
|
+
this._headingSub = this._app.streambundle.getSelfStream('navigation.headingTrue')
|
|
121
|
+
.onValue(
|
|
122
|
+
(pos) => {
|
|
123
|
+
this.GPSLog.heading=heading
|
|
124
|
+
|
|
125
|
+
this.emit("GPSLog",this.GPSLog)
|
|
126
|
+
}
|
|
127
|
+
);
|
|
114
128
|
}
|
|
115
129
|
|
|
116
130
|
async stopListening(){
|
|
@@ -118,6 +132,10 @@ class AbstractBeaconMixin {
|
|
|
118
132
|
this._positionSub()
|
|
119
133
|
this._positionSub = null
|
|
120
134
|
}
|
|
135
|
+
if (this._headingSub){
|
|
136
|
+
this._headingSub()
|
|
137
|
+
this._headingSub = null
|
|
138
|
+
}
|
|
121
139
|
}
|
|
122
140
|
|
|
123
141
|
}
|
|
@@ -301,6 +301,16 @@ class GobiusCTankMeter extends BTSensor{
|
|
|
301
301
|
this.addDefaultParam("id")
|
|
302
302
|
.default="1"
|
|
303
303
|
|
|
304
|
+
this.addParameter("capacity",
|
|
305
|
+
{
|
|
306
|
+
title:"capacity of tank in m3 (liters/1000)",
|
|
307
|
+
type:"number",
|
|
308
|
+
unit:"m3",
|
|
309
|
+
isRequired: true,
|
|
310
|
+
default:10000
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
|
|
304
314
|
this.addMetadatum("mst","","Device state",
|
|
305
315
|
(buffer)=>{return GobiusState.get(buffer.readInt8(0))}
|
|
306
316
|
)
|
|
@@ -322,6 +332,11 @@ class GobiusCTankMeter extends BTSensor{
|
|
|
322
332
|
)
|
|
323
333
|
.default='tanks.{type}.{id}.currentLevel'
|
|
324
334
|
|
|
335
|
+
this.addMetadatum("mfr","m3","Remaining volume",
|
|
336
|
+
(buffer)=>{return this.capacity*(buffer.readInt16BE(3)/1000)}
|
|
337
|
+
)
|
|
338
|
+
.default='tanks.{type}.{id}.remaining'
|
|
339
|
+
|
|
325
340
|
this.addMetadatum("minc","rad","Sensor inclination",
|
|
326
341
|
(buffer)=>{return buffer.readInt8(5) * Math.PI/180}
|
|
327
342
|
)
|
|
@@ -16,19 +16,25 @@ const VictronIdentifier = require('./VictronIdentifier.js');
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
static async getDataPacket(device, md){
|
|
19
|
+
static async getDataPacket(device, md, timeout=60000) {
|
|
20
20
|
if (md && md[this.ManufacturerID]?.value[0]==0x10)
|
|
21
21
|
return md[this.ManufacturerID].value
|
|
22
22
|
|
|
23
23
|
device.helper._prepare()
|
|
24
24
|
|
|
25
25
|
return new Promise((resolve, reject) => {
|
|
26
|
+
const timeoutID = setTimeout(() => {
|
|
27
|
+
device.helper.removeListeners()
|
|
28
|
+
reject(new Error("Timeout waiting for Victron Manufacturer Data"))
|
|
29
|
+
}, timeout);
|
|
30
|
+
|
|
26
31
|
device.helper.on("PropertiesChanged",
|
|
27
32
|
(props)=> {
|
|
28
33
|
if (Object.hasOwn(props,'ManufacturerData')){
|
|
29
34
|
const md = props['ManufacturerData'].value
|
|
30
35
|
if(md[this.ManufacturerID].value[0]==0x10) {
|
|
31
36
|
device.helper.removeListeners()
|
|
37
|
+
clearTimeout(timeoutID);
|
|
32
38
|
resolve(md[this.ManufacturerID].value)
|
|
33
39
|
}
|
|
34
40
|
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
const RadialRing = ({
|
|
3
|
+
size = 400,
|
|
4
|
+
radius = 200,
|
|
5
|
+
centerOffset = {x:0, y:0},
|
|
6
|
+
offsets = { inner: 0.6, middle: 0.8 },
|
|
7
|
+
colors = { primary: "#ff0000", accent: "#ff0000" }, pulse=false
|
|
8
|
+
}) => {
|
|
9
|
+
|
|
10
|
+
const cx = size / 2;
|
|
11
|
+
const cy = size / 2
|
|
12
|
+
const pulsar=`
|
|
13
|
+
@keyframes pulse {
|
|
14
|
+
0% {
|
|
15
|
+
transform: scale(.5);
|
|
16
|
+
opacity: 0.8;
|
|
17
|
+
}
|
|
18
|
+
100% {
|
|
19
|
+
transform: scale(1);
|
|
20
|
+
opacity: 0;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
const ringStyle = {
|
|
25
|
+
transformOrigin: 'center',
|
|
26
|
+
animation: pulse ? `pulse 2s ease-out infinite` : 'none',
|
|
27
|
+
};
|
|
28
|
+
return (
|
|
29
|
+
<div>
|
|
30
|
+
<style>{pulsar}</style>
|
|
31
|
+
<svg
|
|
32
|
+
viewBox={`${0} ${0} ${size} ${size}`}
|
|
33
|
+
>
|
|
34
|
+
<defs>
|
|
35
|
+
<radialGradient id="ringGradient">
|
|
36
|
+
<stop offset={offsets.inner} stopColor={colors.primary} stopOpacity={0}/>
|
|
37
|
+
<stop offset={offsets.middle} stopColor={colors.accent} stopOpacity={1}/>
|
|
38
|
+
<stop offset={1} stopColor={colors.primary} stopOpacity={0}/>
|
|
39
|
+
</radialGradient>
|
|
40
|
+
</defs>
|
|
41
|
+
<g
|
|
42
|
+
transform={`translate(${centerOffset.x}, ${-1*centerOffset.y})`}>
|
|
43
|
+
|
|
44
|
+
<circle cx={cx} cy={cy} r={radius} fill="url(#ringGradient)" style={ringStyle} />
|
|
45
|
+
</g>
|
|
46
|
+
</svg>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const FuzzyDistance = ({ distance = 10, accuracy = 0.75, size = 200, centerOffset={x:0, y:0}, scale=100, pulse=false }) => {
|
|
52
|
+
const ptom = size / scale;
|
|
53
|
+
const delta = (((1 - (accuracy>=1?.96:accuracy)) * distance))
|
|
54
|
+
const r = (distance + delta)*ptom;
|
|
55
|
+
const innerR = (distance - delta)*ptom;
|
|
56
|
+
|
|
57
|
+
const offset0 = innerR/r
|
|
58
|
+
const offset1 = (1 + (innerR / r)) / 2;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<RadialRing
|
|
62
|
+
pulse={pulse}
|
|
63
|
+
radius={r}
|
|
64
|
+
centerOffset={{x:centerOffset.x*ptom, y: centerOffset.y*ptom}}
|
|
65
|
+
offsets={{ inner: offset0, middle: offset1 }}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
const Boat = ({
|
|
72
|
+
lengthMeters = 5,
|
|
73
|
+
widthMeters = 2,
|
|
74
|
+
offset = { x: 0, y: 0 }
|
|
75
|
+
}) => {
|
|
76
|
+
// We use a constant internal coordinate system (e.g., 1 unit = 1 meter)
|
|
77
|
+
// The viewBox will handle the scaling to the actual pixel size
|
|
78
|
+
const halfW = widthMeters / 2;
|
|
79
|
+
const halfL = lengthMeters / 2;
|
|
80
|
+
const padding = 0 ; // 1 meter of padding around the boat
|
|
81
|
+
|
|
82
|
+
// Calculate the viewBox to ensure the boat and the dot are always visible
|
|
83
|
+
const minX = -Math.max(halfW, Math.abs(offset.x)) - padding;
|
|
84
|
+
const minY = -Math.max(halfL, Math.abs(offset.y)) - padding;
|
|
85
|
+
const width = (Math.max(halfW, Math.abs(offset.x)) + padding) * 2;
|
|
86
|
+
const height = (Math.max(halfL, Math.abs(offset.y)) + padding) * 2;
|
|
87
|
+
|
|
88
|
+
// Path data using SVG Command syntax:
|
|
89
|
+
// M = MoveTo, C = Cubic Bezier, L = LineTo, Z = ClosePath
|
|
90
|
+
const hullPath = `
|
|
91
|
+
M 0 ${-halfL}
|
|
92
|
+
C ${halfW * 1.2} ${-halfL * 0.5}, ${halfW} ${halfL * 0.5}, ${halfW * 0.8} ${halfL}
|
|
93
|
+
L ${-halfW * 0.8} ${halfL}
|
|
94
|
+
C ${-halfW} ${halfL * 0.5}, ${-halfW * 1.2} ${-halfL * 0.5}, 0 ${-halfL}
|
|
95
|
+
Z
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<svg
|
|
101
|
+
viewBox={`${minX} ${minY} ${width} ${height}`}
|
|
102
|
+
style={{ width: '100%', height: '100%' }}
|
|
103
|
+
>
|
|
104
|
+
{/* The Boat Hull */}
|
|
105
|
+
<path
|
|
106
|
+
d={hullPath}
|
|
107
|
+
fill="white"
|
|
108
|
+
stroke="#333"
|
|
109
|
+
strokeWidth={widthMeters * 0.02}
|
|
110
|
+
strokeLinejoin="round"
|
|
111
|
+
/>
|
|
112
|
+
|
|
113
|
+
<g
|
|
114
|
+
transform={`translate(${offset.x}, ${-1*offset.y})`}
|
|
115
|
+
stroke="#211d3a77"
|
|
116
|
+
strokeWidth={widthMeters * 0.015}
|
|
117
|
+
>
|
|
118
|
+
{/* Horizontal line */}
|
|
119
|
+
<line x1="-0.3" y1="0" x2="0.3" y2="0" />
|
|
120
|
+
{/* Vertical line */}
|
|
121
|
+
<line x1="0" y1="-0.3" x2="0" y2="0.3" />
|
|
122
|
+
</g>
|
|
123
|
+
</svg>
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
function formatMilliseconds(ms) {
|
|
127
|
+
const seconds = Math.floor((ms / 1000) % 60);
|
|
128
|
+
const minutes = Math.floor((ms / (1000 * 60)) % 60);
|
|
129
|
+
const hours = Math.floor((ms / (1000 * 60 * 60)) % 24);
|
|
130
|
+
const days = Math.floor(ms / (1000 * 60 * 60 * 24));
|
|
131
|
+
|
|
132
|
+
// Format with leading zeros
|
|
133
|
+
return `${days}${days>0?'d':''} ${days>0|hours>0?String(hours).padStart(2, '0')+'h':''} ${hours>0|minutes>0?String(minutes).padStart(2, '0')+'m':''} ${String(seconds).padStart(2, '0')}s`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Calculates the nautical distance between two points on Earth.
|
|
138
|
+
* @param {number} lat1 - Latitude of first point
|
|
139
|
+
* @param {number} lon1 - Longitude of first point
|
|
140
|
+
* @param {number} lat2 - Latitude of second point
|
|
141
|
+
* @param {number} lon2 - Longitude of second point
|
|
142
|
+
* @returns {number} Distance in Nautical Miles (NM)
|
|
143
|
+
*//**
|
|
144
|
+
* Calculates the nautical distance between two points on Earth.
|
|
145
|
+
* @param {number} lat1 - Latitude of first point
|
|
146
|
+
* @param {number} lon1 - Longitude of first point
|
|
147
|
+
* @param {number} lat2 - Latitude of second point
|
|
148
|
+
* @param {number} lon2 - Longitude of second point
|
|
149
|
+
* @returns {number} Distance in Nautical Miles (NM)
|
|
150
|
+
*/
|
|
151
|
+
function calculateNauticalDistance(lat1, lon1, lat2, lon2) {
|
|
152
|
+
const R = 3440.065; // Earth's radius in Nautical Miles
|
|
153
|
+
const dLat = (lat2 - lat1) * (Math.PI / 180);
|
|
154
|
+
const dLon = (lon2 - lon1) * (Math.PI / 180);
|
|
155
|
+
|
|
156
|
+
const a =
|
|
157
|
+
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
158
|
+
Math.cos(lat1 * (Math.PI / 180)) *
|
|
159
|
+
Math.cos(lat2 * (Math.PI / 180)) *
|
|
160
|
+
Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
|
161
|
+
|
|
162
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
163
|
+
const d = R * c; // Distance in NM
|
|
164
|
+
|
|
165
|
+
return d;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getLatLonOffset(lat1, lon1, lat2, lon2) {
|
|
169
|
+
const R = 6371000; // Earth radius in meters
|
|
170
|
+
const toRad = (deg) => (deg * Math.PI) / 180;
|
|
171
|
+
|
|
172
|
+
// 1. Calculate the difference in Radians
|
|
173
|
+
const dLat = toRad(lat2 - lat1);
|
|
174
|
+
const dLon = toRad(lon2 - lon1);
|
|
175
|
+
const latAvg = toRad((lat1 + lat2) / 2);
|
|
176
|
+
|
|
177
|
+
// 2. Apply the projection
|
|
178
|
+
const dy = toRad(lat2 - lat1) * R;
|
|
179
|
+
const dx = dLon * R * Math.cos(latAvg);
|
|
180
|
+
|
|
181
|
+
return { x: dx, y: dy };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function getBearing(lat1, lon1, lat2, lon2) {
|
|
185
|
+
// Convert degrees to radians
|
|
186
|
+
const toRadians = (degrees) => (degrees * Math.PI) / 180;
|
|
187
|
+
const toDegrees = (radians) => (radians * 180) / Math.PI;
|
|
188
|
+
|
|
189
|
+
const startLat = toRadians(lat1);
|
|
190
|
+
const startLng = toRadians(lon1);
|
|
191
|
+
const destLat = toRadians(lat2);
|
|
192
|
+
const destLng = toRadians(lon2);
|
|
193
|
+
|
|
194
|
+
const y = Math.sin(destLng - startLng) * Math.cos(destLat);
|
|
195
|
+
const x = Math.cos(startLat) * Math.sin(destLat) -
|
|
196
|
+
Math.sin(startLat) * Math.cos(destLat) * Math.cos(destLng - startLng);
|
|
197
|
+
|
|
198
|
+
let bearing = Math.atan2(y, x);
|
|
199
|
+
bearing = toDegrees(bearing);
|
|
200
|
+
|
|
201
|
+
// Normalize to 0-360 degrees
|
|
202
|
+
return (bearing + 360) % 360;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const BeaconRenderer = ({value, centerOffset={x:0,y:0}, size}) => {
|
|
206
|
+
const [index, setIndex] = useState(0);
|
|
207
|
+
const [distanceTo, setDistanceTo ] = useState(calculateNauticalDistance(value.latitude, value.longitude, value[index].latitude, value[index].longitude ))
|
|
208
|
+
|
|
209
|
+
const latLonOffset = getLatLonOffset( value.log[index].latitude, value.log[index].longitude, value.latitude, value.longitude )
|
|
210
|
+
|
|
211
|
+
const minSize=Math.max(Math.abs(latLonOffset.x*1.5),Math.abs(latLonOffset.y*1.5))
|
|
212
|
+
const spriteScale=minSize<beam?0.9:scale/2
|
|
213
|
+
const ptom = size/(loa/scale)
|
|
214
|
+
console.log(scale, value.loa/(1/scale), ptom, latLonOffset.x*ptom, latLonOffset.y*ptom)
|
|
215
|
+
return (
|
|
216
|
+
<div>
|
|
217
|
+
|
|
218
|
+
<div style={{ width: size, height: size, background: '#42b9f5', border: '1px solid #ccc', position:'absolute' }}>
|
|
219
|
+
<div style={{ transform: [
|
|
220
|
+
`translateX(${latLonOffset.x*ptom/2}px)
|
|
221
|
+
translateY(${latLonOffset.y*ptom/2}px)`]
|
|
222
|
+
}}>
|
|
223
|
+
<div style={{ zIndex:1, width: size, height: size, position: 'absolute',
|
|
224
|
+
transform: [`scale(${spriteScale})
|
|
225
|
+
rotate(${value.heading}rad)`]}}>
|
|
226
|
+
<Boat lengthMeters={value.loa} widthMeters={value.beam} offset={centerOffset} />
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
<div style={{ transform: [`scale(${spriteScale})`], width: size, height: size, position: 'absolute', zIndex:2}}>
|
|
230
|
+
<FuzzyDistance pulse={distanceTo*1852>(value.loa/2)} scale={(value.loa)} accuracy={value.log[index].distances.accuracy} distance={value.log[index].distances.avgDistance} centerOffset={centerOffset} size={size}/>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
<div style={{ color:"black", fontSize: `${size/22}px`}}>
|
|
234
|
+
<div style={{ position: 'absolute', top:10, left: 10}}>
|
|
235
|
+
|
|
236
|
+
{value.log[0].latitude}, {value.log[0].longitude} <p/>
|
|
237
|
+
{formatMilliseconds(timeNow-new Date(value.log[index].timestamp).valueOf())}
|
|
238
|
+
</div>
|
|
239
|
+
<div style={{ position: 'absolute', bottom:10, left:10 }}>
|
|
240
|
+
{distanceTo>.25?distanceTo.toFixed(2)+'nm':(distanceTo*1852).toFixed(2)+'m'} {getBearing(value.latitude, value.longitude, value.log[index].latitude, value.log[index].longitude ).toFixed(2)}°
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
<div style={{ color:"black", position: 'absolute', bottom:10, right:40 }}>
|
|
244
|
+
<button
|
|
245
|
+
onClick={() => {setIndex(index==0?value.log.length-1:index - 1)}}
|
|
246
|
+
>
|
|
247
|
+
◀
|
|
248
|
+
</button>
|
|
249
|
+
</div>
|
|
250
|
+
<div style={{ color:"black", position: 'absolute', bottom:10, right: 10}}>
|
|
251
|
+
<button
|
|
252
|
+
onClick={() => {setIndex(index==value.log.length-1?0:index + 1)}}
|
|
253
|
+
>
|
|
254
|
+
►
|
|
255
|
+
</button>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
);
|
|
260
|
+
};
|
|
261
|
+
export default BeaconRenderer
|