@rhizomatics/signalk-einklabel-plugin 0.4.1 → 0.4.3
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/CHANGELOG.md +9 -0
- package/README.md +8 -3
- package/dist/cli/liveContext.js +2 -1
- package/dist/config.js +1 -1
- package/dist/render/binding.d.ts +1 -1
- package/dist/render/binding.js +1 -1
- package/dist/render/formatters.d.ts +10 -0
- package/dist/render/formatters.js +42 -14
- package/dist/render/svgRenderer.d.ts +1 -1
- package/dist/render/svgRenderer.js +11 -3
- package/dist/repaintScheduler.js +5 -1
- package/docs/assets/screenshots/example_tidal_clock.png +0 -0
- package/package.json +1 -1
- package/templates/tide.svg +131 -80
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
# 0.4.3
|
|
2
|
+
- Fixed datetime values when running from plugin were blank while CLI was fine
|
|
3
|
+
- Added an example Resources API output from signalk-tides plugin
|
|
4
|
+
- Relaid out the example tide clock, adding the tidal range (LAT to HAT) and the source of tide data
|
|
5
|
+
- Alternative source of time zone info, `source=einklabel,path=local_zone`
|
|
6
|
+
# 0.4.2
|
|
7
|
+
- Fix High/Low display for tide clock template
|
|
8
|
+
- Fix last repaint time for display being hour out
|
|
9
|
+
- Change default name of template directory to 'einklabel/templates'
|
|
1
10
|
# 0.4.1
|
|
2
11
|
- Packaging fixes
|
|
3
12
|
# 0.4.0
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
** BETA - basic functionality, limited vendor/product support **
|
|
4
4
|
|
|
5
|
-
A SignalK plugin to display data from SignalK paths, APIs and plugins on Electronic Shelf Labels over a Bluetooth Low Energy (BLE) connection.
|
|
5
|
+
A SignalK plugin to display data from SignalK paths, APIs and plugins on Electronic Shelf Labels over a Bluetooth Low Energy (BLE) connection using simple SVG templates.
|
|
6
6
|
|
|
7
7
|
Electronic Shelf Labels (ESLs) are [eInk](https://en.wikipedia.org/wiki/E_Ink) devices that consume very little battery energy, presuming they are not constantly updated - the battery is used only when the display changes, and a periodic BLE check for incoming changes. Perfect for info that changes only once or twice a day, like tidal information.
|
|
8
8
|
|
|
@@ -30,13 +30,16 @@ In the `description` of the SVG text box, use a comma separated set of key value
|
|
|
30
30
|
|
|
31
31
|
For example, `path=environment.forecast.description` uses the default data source (the `self` vessel context) and the named SignalK path. A bare path with no key/value pairs at all, e.g. just `environment.forecast.description`, is shorthand for the same thing. Overriding the default context can be done with `path=environment.forecast.description,context=vessels.urn:mrn:imo:mmsi:232345678` - the `context` value must match a real SignalK context exactly as it appears in the Data Browser.
|
|
32
32
|
|
|
33
|
-
####
|
|
33
|
+
#### SignalK REST APIs
|
|
34
34
|
|
|
35
35
|
The source can be overridden to use the SignalK server's Resources API instead. For example, `source=resources,resource=tides,path=station.name` picks the `tides` resource and pulls the `station.name` path out of the JSON response - this works for any resource type (`tides`, `waypoints`, `routes`, ...), and needs nothing configured: the plugin reaches the Resources API directly. Where a resource is specified, it will be fetched once for that render, and subsequent fields sourced from the same resource use that cached response.
|
|
36
36
|
|
|
37
37
|
#### Plugin Data
|
|
38
38
|
|
|
39
|
-
`source=einklabel` reads data injected by the plugin itself, rather than from SignalK.
|
|
39
|
+
`source=einklabel` reads data injected by the plugin itself, rather than from SignalK. Available paths:
|
|
40
|
+
|
|
41
|
+
* `path=repainted` - the timestamp of the current repaint - for example `source=einklabel,path=repainted,format=local_datetime_short` to show when the label was last updated.
|
|
42
|
+
* `path=local_zone` - a short zone name (e.g. `BST`) for the same timezone used for `local_time`/`day_mon`/`local_datetime_short` (see above) - a fallback for `environment.time.timezoneRegion,format=utc_offset` on installs that never publish that path, since it needs no SignalK metadata of its own. Falls back to a plain UTC offset like `GMT+1` where the host's locale has no real abbreviation for the zone.
|
|
40
43
|
|
|
41
44
|
#### Customizing Output
|
|
42
45
|
|
|
@@ -51,6 +54,8 @@ A `format` can be specified to make the value easier to understand. The supporte
|
|
|
51
54
|
|
|
52
55
|
SignalK's unit preferences are used to automatically convert a `signalk`-sourced numeric value to its preferred display unit, and append a unit symbol like `kt` or `m`, unless `format=raw` is specified to switch that off. However, when using plugin or API data there may be no path metadata to convert from (for example `signalk-tides` publishes tide data to the Resources API, and `level` is a raw metre value with no SignalK path of its own) - in these cases an explicit `category` can be given instead, and the unit preferences will be applied the same way, for example `category=depth` for the tides level figure.
|
|
53
56
|
|
|
57
|
+
Note that for dates and times, the server timezone must be set correctly, for example `Europe/London` rather than the default `Etc/UTC`. This can be done on Linux using `timedatectl` or `raspi-config` if using a Raspberry Pi with Raspian.
|
|
58
|
+
|
|
54
59
|
Common categories:
|
|
55
60
|
|
|
56
61
|
* `depth` - Use the SignalK preferred depth unit, make the conversion if needed, and tack on the unit name as a suffix
|
package/dist/cli/liveContext.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.assembleLiveContext = assembleLiveContext;
|
|
4
|
+
const formatters_1 = require("../render/formatters");
|
|
4
5
|
const httpJson_1 = require("../httpJson");
|
|
5
6
|
const unitCategories_1 = require("../unitCategories");
|
|
6
7
|
const pathMeta_1 = require("../pathMeta");
|
|
@@ -71,6 +72,6 @@ async function assembleLiveContext(signalkUrl, bindings) {
|
|
|
71
72
|
const categories = await (0, unitCategories_1.fetchCategoryDisplayUnits)(signalkUrl, categoryNames);
|
|
72
73
|
// Matches `considerRepaint` in repaintScheduler.ts - the CLI has no real device repaint to time, so
|
|
73
74
|
// a `source=einklabel,path=repainted` binding just resolves to "now", same as a live render would.
|
|
74
|
-
const meta = { repainted: new Date().toISOString() };
|
|
75
|
+
const meta = { repainted: new Date().toISOString(), local_zone: (0, formatters_1.resolveLocalZoneAbbreviation)({ signalk }) };
|
|
75
76
|
return { signalk, resources, pathMeta, categories, meta };
|
|
76
77
|
}
|
package/dist/config.js
CHANGED
|
@@ -13,7 +13,7 @@ const resolveApiUrl_1 = require("./resolveApiUrl");
|
|
|
13
13
|
/** The package's own bundled `templates/` directory (ships alongside `dist/`, see package.json's `files`) - templates here are always available, but a same-named template in the user's `templatesDir` takes priority. */
|
|
14
14
|
const BUNDLED_TEMPLATES_DIR = (0, path_1.join)(__dirname, '..', 'templates');
|
|
15
15
|
const SIGNALK_HOME_DIR = (0, path_1.join)((0, os_1.homedir)(), '.signalk');
|
|
16
|
-
const DEFAULT_TEMPLATES_DIR = (0, path_1.join)(SIGNALK_HOME_DIR, '
|
|
16
|
+
const DEFAULT_TEMPLATES_DIR = (0, path_1.join)(SIGNALK_HOME_DIR, 'einklabel', 'templates');
|
|
17
17
|
function defaultConfig() {
|
|
18
18
|
return {
|
|
19
19
|
templatesDir: '',
|
package/dist/render/binding.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export interface Binding {
|
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* Parses a `<desc>` element's text content into a `Binding`, e.g.
|
|
23
|
-
* `source=resources,resource=tides,path=extremes
|
|
23
|
+
* `source=resources,resource=tides,path=extremes[0].level,category=depth,round=2` or, using the
|
|
24
24
|
* defaults (`source=signalk,context=self`), plain `path=navigation.speedOverGround` (auto-converts via
|
|
25
25
|
* that path's own metadata - see `renderBinding`). A bare path with no `key=value` pairs at all, e.g.
|
|
26
26
|
* `environment.forecast.description`, is shorthand for `path=environment.forecast.description`
|
package/dist/render/binding.js
CHANGED
|
@@ -10,7 +10,7 @@ const SOURCES = ['signalk', 'resources', 'einklabel'];
|
|
|
10
10
|
const KNOWN_KEYS = new Set(['source', 'context', 'resource', 'path', 'format', 'category', 'round']);
|
|
11
11
|
/**
|
|
12
12
|
* Parses a `<desc>` element's text content into a `Binding`, e.g.
|
|
13
|
-
* `source=resources,resource=tides,path=extremes
|
|
13
|
+
* `source=resources,resource=tides,path=extremes[0].level,category=depth,round=2` or, using the
|
|
14
14
|
* defaults (`source=signalk,context=self`), plain `path=navigation.speedOverGround` (auto-converts via
|
|
15
15
|
* that path's own metadata - see `renderBinding`). A bare path with no `key=value` pairs at all, e.g.
|
|
16
16
|
* `environment.forecast.description`, is shorthand for `path=environment.forecast.description`
|
|
@@ -14,5 +14,15 @@ export interface DisplayUnits {
|
|
|
14
14
|
}
|
|
15
15
|
/** Converts a base-SI value (always what SignalK paths deliver) to its metadata's preferred display unit and formats it with the unit's symbol, e.g. 3.42 -> "11.2ft". */
|
|
16
16
|
export declare function formatDisplayUnits(value: number, displayUnits: DisplayUnits, round: number | undefined): string;
|
|
17
|
+
/**
|
|
18
|
+
* Short zone name (e.g. "BST", or "GMT+1" where the host's own `Intl` locale has no real abbreviation
|
|
19
|
+
* for the zone) for the same zone `selfTimezone` resolves - an alternative for
|
|
20
|
+
* `environment.time.timezoneRegion,format=utc_offset` on installs that never publish that path (see
|
|
21
|
+
* `selfTimezone`'s own comment). Uses the host's default locale rather than pinning one, so it shows
|
|
22
|
+
* whatever `Intl` would show the host's own user - "GMT+1" is an acceptable fallback where that locale
|
|
23
|
+
* doesn't have a 3-letter abbreviation to offer. Resolved as a `source=einklabel,path=local_zone`
|
|
24
|
+
* binding (see `considerRepaint`).
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveLocalZoneAbbreviation(context: TemplateContext): string;
|
|
17
27
|
/** Applies a named `format=` formatter to a resolved binding value. */
|
|
18
28
|
export declare function applyFormat(name: string, value: unknown, context: TemplateContext, round: number | undefined): string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.formatDisplayUnits = formatDisplayUnits;
|
|
4
|
+
exports.resolveLocalZoneAbbreviation = resolveLocalZoneAbbreviation;
|
|
4
5
|
exports.applyFormat = applyFormat;
|
|
5
6
|
const luxon_1 = require("luxon");
|
|
6
7
|
const mathjs_1 = require("mathjs");
|
|
@@ -14,13 +15,46 @@ function formatDisplayUnits(value, displayUnits, round) {
|
|
|
14
15
|
/**
|
|
15
16
|
* The local vessel's timezone (`signalk.self.environment.time.timezoneRegion`), regardless of which
|
|
16
17
|
* vessel's value is being formatted - the display's own clock/locale is what matters, not the data
|
|
17
|
-
* source's.
|
|
18
|
+
* source's. Most installs never publish that path (it needs a GPS/position-derived timezone plugin),
|
|
19
|
+
* so this falls back to the host machine's own configured zone - the same thing the SignalK Data
|
|
20
|
+
* Browser ends up showing, since it's just the browser's local `Intl` zone rather than anything the
|
|
21
|
+
* server itself publishes. For the live plugin that's the device's own OS timezone (set once via
|
|
22
|
+
* `raspi-config`/`timedatectl` et al, not GPS-derived); for the CLI it's the developer machine's.
|
|
18
23
|
*/
|
|
19
24
|
function selfTimezone(context) {
|
|
20
25
|
const signalk = context.signalk;
|
|
21
26
|
const self = signalk?.self;
|
|
22
27
|
const environment = self?.environment;
|
|
23
|
-
return environment?.time?.timezoneRegion ||
|
|
28
|
+
return environment?.time?.timezoneRegion || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Short zone name (e.g. "BST", or "GMT+1" where the host's own `Intl` locale has no real abbreviation
|
|
32
|
+
* for the zone) for the same zone `selfTimezone` resolves - an alternative for
|
|
33
|
+
* `environment.time.timezoneRegion,format=utc_offset` on installs that never publish that path (see
|
|
34
|
+
* `selfTimezone`'s own comment). Uses the host's default locale rather than pinning one, so it shows
|
|
35
|
+
* whatever `Intl` would show the host's own user - "GMT+1" is an acceptable fallback where that locale
|
|
36
|
+
* doesn't have a 3-letter abbreviation to offer. Resolved as a `source=einklabel,path=local_zone`
|
|
37
|
+
* binding (see `considerRepaint`).
|
|
38
|
+
*/
|
|
39
|
+
function resolveLocalZoneAbbreviation(context) {
|
|
40
|
+
const part = new Intl.DateTimeFormat(undefined, { timeZone: selfTimezone(context), timeZoneName: 'short' })
|
|
41
|
+
.formatToParts(new Date())
|
|
42
|
+
.find((p) => p.type === 'timeZoneName');
|
|
43
|
+
return part?.value ?? '';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A `resources`-sourced timestamp can arrive as either an ISO string (always true over HTTP, since
|
|
47
|
+
* `JSON.stringify` converts a `Date` to its ISO string automatically) or a live `Date` instance (an
|
|
48
|
+
* in-process `app.resourcesApi` call skips that serialisation step entirely, so a provider that hasn't
|
|
49
|
+
* itself called `.toISOString()` yet hands back the raw object) - accept both so the live plugin and
|
|
50
|
+
* the CLI render identically regardless of which path a binding's data took.
|
|
51
|
+
*/
|
|
52
|
+
function parseTimestamp(value) {
|
|
53
|
+
if (value instanceof Date)
|
|
54
|
+
return luxon_1.DateTime.fromJSDate(value, { zone: 'utc' });
|
|
55
|
+
if (typeof value === 'string')
|
|
56
|
+
return luxon_1.DateTime.fromISO(value, { zone: 'utc' });
|
|
57
|
+
return undefined;
|
|
24
58
|
}
|
|
25
59
|
/**
|
|
26
60
|
* Shows the explicit IANA zone name rather than an abbreviation (e.g. "BST") - UK tide tables are
|
|
@@ -28,24 +62,18 @@ function selfTimezone(context) {
|
|
|
28
62
|
* just locally styled.
|
|
29
63
|
*/
|
|
30
64
|
function formatLocalTime(value, context) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const dt = luxon_1.DateTime.fromISO(value, { zone: 'utc' }).setZone(selfTimezone(context));
|
|
34
|
-
return dt.isValid ? dt.toFormat('HH:mm') : '';
|
|
65
|
+
const dt = parseTimestamp(value)?.setZone(selfTimezone(context));
|
|
66
|
+
return dt?.isValid ? dt.toFormat('HH:mm') : '';
|
|
35
67
|
}
|
|
36
68
|
/** e.g. "27 Jun" - day of month (no leading zero) and abbreviated month name, in the local vessel's timezone. */
|
|
37
69
|
function formatDayMonth(value, context) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const dt = luxon_1.DateTime.fromISO(value, { zone: 'utc' }).setZone(selfTimezone(context));
|
|
41
|
-
return dt.isValid ? dt.toFormat('d MMM') : '';
|
|
70
|
+
const dt = parseTimestamp(value)?.setZone(selfTimezone(context));
|
|
71
|
+
return dt?.isValid ? dt.toFormat('d MMM') : '';
|
|
42
72
|
}
|
|
43
73
|
/** e.g. "21 Jun 26 18:05" - day of month (no leading zero), abbreviated month, 2-digit year, and 24h time, in the local vessel's timezone. */
|
|
44
74
|
function formatLocalDatetimeShort(value, context) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const dt = luxon_1.DateTime.fromISO(value, { zone: 'utc' }).setZone(selfTimezone(context));
|
|
48
|
-
return dt.isValid ? dt.toFormat('d MMM yy HH:mm') : '';
|
|
75
|
+
const dt = parseTimestamp(value)?.setZone(selfTimezone(context));
|
|
76
|
+
return dt?.isValid ? dt.toFormat('d MMM yy HH:mm') : '';
|
|
49
77
|
}
|
|
50
78
|
/**
|
|
51
79
|
* IANA region names are ambiguous about DST (e.g. "Europe/London" is UTC+00:00 in winter, UTC+01:00
|
|
@@ -5,7 +5,7 @@ import { Bitmap, Renderer, TemplateContext } from './types';
|
|
|
5
5
|
* Binding model: a `<text>` element with a `<desc>` child has that child's
|
|
6
6
|
* content parsed as a flat `key=value,key=value` binding against the render
|
|
7
7
|
* context and substituted in as the element's text, e.g.
|
|
8
|
-
* `<desc>source=resources,resource=tides,path=extremes
|
|
8
|
+
* `<desc>source=resources,resource=tides,path=extremes[0].time,format=local_time</desc>`
|
|
9
9
|
* (see `./binding.ts` for the grammar and `./formatters.ts` for the `format=` registry).
|
|
10
10
|
* The `<text>` element's own visible content is left untouched in the source
|
|
11
11
|
* file - it's just a placeholder so the template looks sane while laying it
|
|
@@ -19,7 +19,7 @@ function ensureWasmInitialized() {
|
|
|
19
19
|
* Binding model: a `<text>` element with a `<desc>` child has that child's
|
|
20
20
|
* content parsed as a flat `key=value,key=value` binding against the render
|
|
21
21
|
* context and substituted in as the element's text, e.g.
|
|
22
|
-
* `<desc>source=resources,resource=tides,path=extremes
|
|
22
|
+
* `<desc>source=resources,resource=tides,path=extremes[0].time,format=local_time</desc>`
|
|
23
23
|
* (see `./binding.ts` for the grammar and `./formatters.ts` for the `format=` registry).
|
|
24
24
|
* The `<text>` element's own visible content is left untouched in the source
|
|
25
25
|
* file - it's just a placeholder so the template looks sane while laying it
|
|
@@ -62,8 +62,16 @@ class SvgRenderer {
|
|
|
62
62
|
const descElement = element.getElementsByTagName('desc').item(0);
|
|
63
63
|
if (!descElement)
|
|
64
64
|
continue;
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
// Scoped to this one element - a single bad/unavailable binding (e.g. a resource that hasn't
|
|
66
|
+
// loaded yet) must not blank out every other field on the label, nor abort the whole repaint.
|
|
67
|
+
try {
|
|
68
|
+
const binding = (0, binding_1.parseBinding)(descElement.textContent ?? '');
|
|
69
|
+
element.textContent = (0, binding_1.renderBinding)(binding, context);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
console.error(`field "${descElement.textContent}" failed to render: ${err.message}`);
|
|
73
|
+
element.textContent = 'ERROR';
|
|
74
|
+
}
|
|
67
75
|
}
|
|
68
76
|
const svgOutput = new xmldom_1.XMLSerializer().serializeToString(doc);
|
|
69
77
|
const resvg = new resvg_wasm_1.Resvg(svgOutput, {
|
package/dist/repaintScheduler.js
CHANGED
|
@@ -9,6 +9,7 @@ const bleDiscovery_1 = require("./devices/bleDiscovery");
|
|
|
9
9
|
const registry_1 = require("./devices/registry");
|
|
10
10
|
const svgRenderer_1 = require("./render/svgRenderer");
|
|
11
11
|
const binding_1 = require("./render/binding");
|
|
12
|
+
const formatters_1 = require("./render/formatters");
|
|
12
13
|
const unitCategories_1 = require("./unitCategories");
|
|
13
14
|
const pathMeta_1 = require("./pathMeta");
|
|
14
15
|
const resolveApiUrl_1 = require("./resolveApiUrl");
|
|
@@ -136,7 +137,10 @@ async function considerRepaint(app, config, device, state, getApiUrl) {
|
|
|
136
137
|
app.debug(`"${device.friendlyName}": data unchanged, skipping repaint`);
|
|
137
138
|
return;
|
|
138
139
|
}
|
|
139
|
-
const renderContext = {
|
|
140
|
+
const renderContext = {
|
|
141
|
+
...rawContext,
|
|
142
|
+
meta: { repainted: new Date().toISOString(), local_zone: (0, formatters_1.resolveLocalZoneAbbreviation)(rawContext) },
|
|
143
|
+
};
|
|
140
144
|
const renderer = new svgRenderer_1.SvgRenderer();
|
|
141
145
|
const bitmap = await renderer.render(templatePath, renderContext, metadata.width, metadata.height - metadata.voffset);
|
|
142
146
|
const connectTimeoutMs = config.paintConnectTimeoutSeconds * 1000;
|
|
Binary file
|
package/package.json
CHANGED
package/templates/tide.svg
CHANGED
|
@@ -22,15 +22,33 @@
|
|
|
22
22
|
inkscape:pageopacity="0.0"
|
|
23
23
|
inkscape:pagecheckerboard="0"
|
|
24
24
|
inkscape:deskcolor="#d1d1d1"
|
|
25
|
-
inkscape:zoom="
|
|
26
|
-
inkscape:cx="
|
|
27
|
-
inkscape:cy="
|
|
25
|
+
inkscape:zoom="1.3906433"
|
|
26
|
+
inkscape:cx="202.4243"
|
|
27
|
+
inkscape:cy="151.36879"
|
|
28
28
|
inkscape:window-width="1488"
|
|
29
29
|
inkscape:window-height="674"
|
|
30
30
|
inkscape:window-x="0"
|
|
31
31
|
inkscape:window-y="33"
|
|
32
32
|
inkscape:window-maximized="0"
|
|
33
|
-
inkscape:current-layer="svg1"
|
|
33
|
+
inkscape:current-layer="svg1"
|
|
34
|
+
showgrid="true">
|
|
35
|
+
<inkscape:grid
|
|
36
|
+
id="grid1"
|
|
37
|
+
units="px"
|
|
38
|
+
originx="0"
|
|
39
|
+
originy="0"
|
|
40
|
+
spacingx="1"
|
|
41
|
+
spacingy="1"
|
|
42
|
+
empcolor="#0099e5"
|
|
43
|
+
empopacity="0.30196078"
|
|
44
|
+
color="#0099e5"
|
|
45
|
+
opacity="0.14901961"
|
|
46
|
+
empspacing="5"
|
|
47
|
+
dotted="false"
|
|
48
|
+
gridanglex="30"
|
|
49
|
+
gridanglez="30"
|
|
50
|
+
visible="true" />
|
|
51
|
+
</sodipodi:namedview>
|
|
34
52
|
<rect
|
|
35
53
|
x="1.0169492"
|
|
36
54
|
y="2.0798673e-08"
|
|
@@ -58,20 +76,18 @@
|
|
|
58
76
|
id="desc1">source=resources,resource=tides,path=station.name</desc></text>
|
|
59
77
|
<text
|
|
60
78
|
id="station.name-8"
|
|
61
|
-
x="
|
|
62
|
-
y="
|
|
79
|
+
x="440.67529"
|
|
80
|
+
y="196.47466"
|
|
63
81
|
font-size="18"
|
|
64
82
|
font-family="serif"
|
|
65
83
|
fill="black"
|
|
66
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#
|
|
67
|
-
transform="scale(0.83037506,1.2042751)"><
|
|
84
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ff0000;stroke-width:2.39571"
|
|
85
|
+
transform="scale(0.83037506,1.2042751)"><desc
|
|
86
|
+
id="desc21">source=resources,resource=tides,path=station.source.name</desc><tspan
|
|
68
87
|
sodipodi:role="line"
|
|
69
|
-
id="
|
|
70
|
-
x="325.49939"
|
|
71
|
-
y="195.87753"
|
|
72
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:2.39571;fill:#999999">signalk-einklabel-plugin</tspan></text>
|
|
88
|
+
id="tspan20">SOURCE</tspan></text>
|
|
73
89
|
<text
|
|
74
|
-
id="
|
|
90
|
+
id="last_repaint"
|
|
75
91
|
x="4.3477793"
|
|
76
92
|
y="201.9895"
|
|
77
93
|
font-size="18"
|
|
@@ -81,10 +97,7 @@
|
|
|
81
97
|
transform="scale(0.85412558,1.170788)"><desc
|
|
82
98
|
id="desc12">source=einklabel,path=repainted,format=local_datetime_short</desc><tspan
|
|
83
99
|
sodipodi:role="line"
|
|
84
|
-
id="tspan3"
|
|
85
|
-
x="4.3477793"
|
|
86
|
-
y="201.9895"
|
|
87
|
-
style="stroke-width:1.19309;-inkscape-font-specification:'Sans, Normal';font-family:Sans;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:13.33333347px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal">Last updated 27 Jun 26 18:05</tspan></text>
|
|
100
|
+
id="tspan3">27 Jun 26 18:05</tspan></text>
|
|
88
101
|
<text
|
|
89
102
|
id="extremes.0"
|
|
90
103
|
x="7.9661016"
|
|
@@ -92,7 +105,8 @@
|
|
|
92
105
|
font-size="14"
|
|
93
106
|
font-family="monospace"
|
|
94
107
|
fill="black"
|
|
95
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.6667px;font-family:monospace;-inkscape-font-specification:'monospace, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ff0000"
|
|
108
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.6667px;font-family:monospace;-inkscape-font-specification:'monospace, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ff0000"><desc
|
|
109
|
+
id="desc13">source=resources,resource=tides,path=extremes[0].label</desc>High</text>
|
|
96
110
|
<text
|
|
97
111
|
id="extremes.1"
|
|
98
112
|
x="13.35283"
|
|
@@ -100,7 +114,8 @@
|
|
|
100
114
|
font-size="14"
|
|
101
115
|
font-family="monospace"
|
|
102
116
|
fill="black"
|
|
103
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.6667px;font-family:monospace;-inkscape-font-specification:'monospace, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ff0000"
|
|
117
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.6667px;font-family:monospace;-inkscape-font-specification:'monospace, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ff0000"><desc
|
|
118
|
+
id="desc14">source=resources,resource=tides,path=extremes[1].label</desc>Low</text>
|
|
104
119
|
<text
|
|
105
120
|
id="extremes.2"
|
|
106
121
|
x="7.9661016"
|
|
@@ -108,123 +123,159 @@
|
|
|
108
123
|
font-size="14"
|
|
109
124
|
font-family="monospace"
|
|
110
125
|
fill="red"
|
|
111
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.6667px;font-family:monospace;-inkscape-font-specification:'monospace, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ff0000"
|
|
126
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.6667px;font-family:monospace;-inkscape-font-specification:'monospace, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ff0000"><desc
|
|
127
|
+
id="desc15">source=resources,resource=tides,path=extremes[2].label</desc>High</text>
|
|
112
128
|
<text
|
|
113
129
|
id="environment.time.timezoneRegion"
|
|
114
|
-
x="
|
|
115
|
-
y="
|
|
130
|
+
x="440.41293"
|
|
131
|
+
y="11.361118"
|
|
132
|
+
font-size="10.373px"
|
|
133
|
+
font-family="sans-serif"
|
|
134
|
+
fill="#ffff00"
|
|
135
|
+
style="direction:rtl;fill:#000000;stroke-width:0.864419"
|
|
136
|
+
transform="scale(0.86441854,1.156847)">UTC+01:00<desc
|
|
137
|
+
id="desc11">source=einklabel,path=local_zone</desc></text>
|
|
138
|
+
<text
|
|
139
|
+
id="lat"
|
|
140
|
+
x="175.5096"
|
|
141
|
+
y="30.720407"
|
|
142
|
+
font-size="12px"
|
|
143
|
+
font-family="sans-serif"
|
|
144
|
+
fill="#ffff00"
|
|
145
|
+
style="fill:#ffff00;-inkscape-font-specification:'Sans, Normal';font-family:Sans;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:18.6667px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke:#a6a6a6;stroke-opacity:1"><desc
|
|
146
|
+
id="desc18">source=resources,resource=tides,path=station.datums.LAT,category=depth</desc><tspan
|
|
147
|
+
sodipodi:role="line"
|
|
148
|
+
id="tspan17">0.1m</tspan></text>
|
|
149
|
+
<text
|
|
150
|
+
id="hat"
|
|
151
|
+
x="264.41321"
|
|
152
|
+
y="30.800503"
|
|
116
153
|
font-size="12px"
|
|
117
154
|
font-family="sans-serif"
|
|
118
155
|
fill="#ffff00"
|
|
119
|
-
style="fill:#
|
|
120
|
-
|
|
156
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.6667px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffff00;stroke:#acacaa;stroke-opacity:1"><desc
|
|
157
|
+
id="desc19">source=resources,resource=tides,path=station.datums.HAT,category=depth</desc><tspan
|
|
158
|
+
sodipodi:role="line"
|
|
159
|
+
id="tspan18"
|
|
160
|
+
x="264.41321"
|
|
161
|
+
y="30.800503">6.0m</tspan></text>
|
|
121
162
|
<text
|
|
122
163
|
id="extremes.2.level"
|
|
123
|
-
x="
|
|
124
|
-
y="
|
|
164
|
+
x="335.54352"
|
|
165
|
+
y="168.08856"
|
|
125
166
|
font-size="14"
|
|
126
167
|
font-family="monospace"
|
|
127
168
|
fill="red"
|
|
128
|
-
style="font-style:normal;font-variant:normal;font-weight:
|
|
129
|
-
transform="scale(0.
|
|
130
|
-
inkscape:transform-center-x="11.
|
|
131
|
-
inkscape:transform-center-y="-
|
|
169
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.33333333px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.78893"
|
|
170
|
+
transform="scale(0.7732368,1.2932649)"
|
|
171
|
+
inkscape:transform-center-x="11.578953"
|
|
172
|
+
inkscape:transform-center-y="-96.464172"><tspan
|
|
132
173
|
sodipodi:role="line"
|
|
133
174
|
id="tspan2-0"
|
|
134
|
-
x="
|
|
135
|
-
y="
|
|
136
|
-
style="font-style:normal;font-variant:normal;font-weight:
|
|
137
|
-
id="desc6">source=resources,resource=tides,path=extremes
|
|
175
|
+
x="335.54352"
|
|
176
|
+
y="168.08856"
|
|
177
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.33333333px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.78893">3.3m</tspan><desc
|
|
178
|
+
id="desc6">source=resources,resource=tides,path=extremes[2].level,category=depth</desc></text>
|
|
138
179
|
<text
|
|
139
180
|
id="extremes.2.time"
|
|
140
|
-
x="
|
|
141
|
-
y="
|
|
181
|
+
x="164.78383"
|
|
182
|
+
y="154.23825"
|
|
142
183
|
font-size="14"
|
|
143
184
|
font-family="monospace"
|
|
144
185
|
fill="red"
|
|
145
|
-
style="font-style:normal;font-variant:normal;font-weight:
|
|
146
|
-
transform="scale(0.
|
|
147
|
-
id="desc10">source=resources,resource=tides,path=extremes
|
|
186
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.33333281px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.51352"
|
|
187
|
+
transform="scale(0.7022624,1.4239692)">15:53<desc
|
|
188
|
+
id="desc10">source=resources,resource=tides,path=extremes[2].time,format=local_time</desc></text>
|
|
148
189
|
<text
|
|
149
190
|
id="extremes.1.level"
|
|
150
|
-
x="
|
|
151
|
-
y="
|
|
191
|
+
x="345.0629"
|
|
192
|
+
y="118.90553"
|
|
152
193
|
font-size="14"
|
|
153
194
|
font-family="monospace"
|
|
154
195
|
fill="red"
|
|
155
|
-
style="font-style:normal;font-variant:normal;font-weight:
|
|
156
|
-
transform="scale(0.
|
|
157
|
-
inkscape:transform-center-y="-
|
|
158
|
-
inkscape:transform-center-x="-0.
|
|
196
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.33333333px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.701"
|
|
197
|
+
transform="scale(0.75163533,1.3304324)"
|
|
198
|
+
inkscape:transform-center-y="-140.38618"
|
|
199
|
+
inkscape:transform-center-x="-0.00038058817"><tspan
|
|
159
200
|
sodipodi:role="line"
|
|
160
201
|
id="tspan2-1"
|
|
161
|
-
x="
|
|
162
|
-
y="
|
|
163
|
-
style="font-style:normal;font-variant:normal;font-weight:
|
|
164
|
-
id="desc5">source=resources,resource=tides,path=extremes
|
|
202
|
+
x="345.0629"
|
|
203
|
+
y="118.90553"
|
|
204
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.33333333px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.701">1.6m</tspan><desc
|
|
205
|
+
id="desc5">source=resources,resource=tides,path=extremes[1].level,category=depth</desc></text>
|
|
165
206
|
<text
|
|
166
207
|
id="extremes.1.time"
|
|
167
|
-
x="
|
|
168
|
-
y="
|
|
208
|
+
x="161.1239"
|
|
209
|
+
y="115.52532"
|
|
169
210
|
font-size="14"
|
|
170
211
|
font-family="monospace"
|
|
171
212
|
fill="red"
|
|
172
|
-
style="font-style:normal;font-variant:normal;font-weight:
|
|
173
|
-
transform="scale(0.
|
|
174
|
-
id="desc8">source=resources,resource=tides,path=extremes
|
|
213
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.33333356px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.48364"
|
|
214
|
+
transform="scale(0.7345432,1.3613903)">09:36<desc
|
|
215
|
+
id="desc8">source=resources,resource=tides,path=extremes[1].time,format=local_time</desc></text>
|
|
175
216
|
<text
|
|
176
217
|
id="extremes.0.time"
|
|
177
|
-
x="
|
|
178
|
-
y="
|
|
218
|
+
x="156.47511"
|
|
219
|
+
y="73.368919"
|
|
179
220
|
font-size="14"
|
|
180
221
|
font-family="monospace"
|
|
181
222
|
fill="red"
|
|
182
|
-
style="font-style:normal;font-variant:normal;font-weight:
|
|
183
|
-
transform="scale(0.
|
|
184
|
-
id="desc3">source=resources,resource=tides,path=extremes
|
|
223
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.33333325px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.55397"
|
|
224
|
+
transform="scale(0.74654856,1.3394976)">03:27<desc
|
|
225
|
+
id="desc3">source=resources,resource=tides,path=extremes[0].time,format=local_time</desc></text>
|
|
185
226
|
<text
|
|
186
227
|
id="extremes.0.time-8"
|
|
187
|
-
x="
|
|
188
|
-
y="
|
|
228
|
+
x="10.326324"
|
|
229
|
+
y="77.594147"
|
|
189
230
|
font-size="14"
|
|
190
231
|
font-family="monospace"
|
|
191
232
|
fill="red"
|
|
192
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:
|
|
233
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:26.6667px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;stroke-width:1.32646"
|
|
193
234
|
transform="scale(0.82428216,1.2131768)"><desc
|
|
194
|
-
id="desc2">source=resources,resource=tides,path=extremes
|
|
235
|
+
id="desc2">source=resources,resource=tides,path=extremes[0].time,format=day_mon</desc>27 Jun</text>
|
|
195
236
|
<text
|
|
196
237
|
id="extremes.0.time-8-5"
|
|
197
|
-
x="
|
|
198
|
-
y="
|
|
238
|
+
x="10.83737"
|
|
239
|
+
y="126.90092"
|
|
199
240
|
font-size="14"
|
|
200
241
|
font-family="monospace"
|
|
201
242
|
fill="red"
|
|
202
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:
|
|
243
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:26.6667px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;stroke-width:1.32646"
|
|
203
244
|
transform="scale(0.82428216,1.2131768)"><desc
|
|
204
|
-
id="desc7">source=resources,resource=tides,path=extremes
|
|
245
|
+
id="desc7">source=resources,resource=tides,path=extremes[1].time,format=day_mon</desc>27 Jun</text>
|
|
205
246
|
<text
|
|
206
247
|
id="extremes.0.time-8-9"
|
|
207
|
-
x="
|
|
208
|
-
y="
|
|
248
|
+
x="12.934491"
|
|
249
|
+
y="161.07082"
|
|
209
250
|
font-size="14"
|
|
210
251
|
font-family="monospace"
|
|
211
252
|
fill="red"
|
|
212
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:
|
|
213
|
-
transform="scale(0.
|
|
214
|
-
id="desc9">source=resources,resource=tides,path=extremes
|
|
253
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:26.6667px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;stroke-width:1.4652"
|
|
254
|
+
transform="scale(0.74623156,1.3400666)"><desc
|
|
255
|
+
id="desc9">source=resources,resource=tides,path=extremes[2].time,format=day_mon</desc>27 Jun</text>
|
|
215
256
|
<text
|
|
216
257
|
id="extremes.0.level"
|
|
217
|
-
x="
|
|
218
|
-
y="
|
|
258
|
+
x="336.3447"
|
|
259
|
+
y="76.456078"
|
|
219
260
|
font-size="14"
|
|
220
261
|
font-family="monospace"
|
|
221
262
|
fill="red"
|
|
222
|
-
style="font-style:normal;font-variant:normal;font-weight:
|
|
223
|
-
transform="scale(0.
|
|
263
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.33333333px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.71992"
|
|
264
|
+
transform="scale(0.7703174,1.2981662)"><tspan
|
|
224
265
|
sodipodi:role="line"
|
|
225
266
|
id="tspan2"
|
|
226
|
-
x="
|
|
227
|
-
y="
|
|
228
|
-
style="font-style:normal;font-variant:normal;font-weight:
|
|
229
|
-
id="desc4">source=resources,resource=tides,path=extremes
|
|
267
|
+
x="336.3447"
|
|
268
|
+
y="76.456078"
|
|
269
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.33333333px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.71992">3.4m</tspan><desc
|
|
270
|
+
id="desc4">source=resources,resource=tides,path=extremes[0].level,category=depth</desc></text>
|
|
271
|
+
<text
|
|
272
|
+
xml:space="preserve"
|
|
273
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.6667px;font-family:Sans;-inkscape-font-specification:'Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffff00;stroke:#acacaa;stroke-width:0.612283;stroke-opacity:1"
|
|
274
|
+
x="232.26662"
|
|
275
|
+
y="30.201849"
|
|
276
|
+
id="text19"><tspan
|
|
277
|
+
sodipodi:role="line"
|
|
278
|
+
id="tspan19"
|
|
279
|
+
x="232.26662"
|
|
280
|
+
y="30.201849">-></tspan></text>
|
|
230
281
|
</svg>
|