incyclist-services 1.7.135 → 1.7.137
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/lib/cjs/i18n/countries/service.js +1 -17
- package/lib/cjs/ride/display/service.js +5 -4
- package/lib/cjs/ride/route/GpxDisplayService.js +3 -3
- package/lib/cjs/ride/route/RLVDisplayService.js +2 -2
- package/lib/cjs/routes/base/parsers/gpx.js +14 -3
- package/lib/cjs/routes/base/parsers/gpxFast.js +203 -0
- package/lib/cjs/routes/base/parsers/incyclist.js +2 -1
- package/lib/cjs/routes/base/parsers/utils.js +15 -10
- package/lib/cjs/routes/base/parsers/xml.js +16 -7
- package/lib/cjs/routes/library/service.js +11 -0
- package/lib/cjs/services/overpass/overpass.js +22 -29
- package/lib/cjs/video/resolvePlaybackUrl.js +1 -3
- package/lib/esm/i18n/countries/service.js +1 -17
- package/lib/esm/ride/display/service.js +5 -4
- package/lib/esm/ride/route/GpxDisplayService.js +3 -3
- package/lib/esm/ride/route/RLVDisplayService.js +2 -2
- package/lib/esm/routes/base/parsers/gpx.js +12 -1
- package/lib/esm/routes/base/parsers/gpxFast.js +199 -0
- package/lib/esm/routes/base/parsers/incyclist.js +2 -1
- package/lib/esm/routes/base/parsers/utils.js +15 -10
- package/lib/esm/routes/base/parsers/xml.js +16 -7
- package/lib/esm/routes/library/service.js +11 -0
- package/lib/esm/services/overpass/overpass.js +22 -26
- package/lib/esm/video/resolvePlaybackUrl.js +1 -3
- package/lib/types/routes/base/parsers/gpx.d.ts +3 -0
- package/lib/types/routes/base/parsers/gpxFast.d.ts +2 -0
- package/lib/types/routes/base/parsers/xml.d.ts +1 -0
- package/lib/types/routes/library/service.d.ts +1 -0
- package/lib/types/routes/library/types.d.ts +2 -0
- package/lib/types/video/resolvePlaybackUrl.d.ts +1 -1
- package/package.json +2 -2
|
@@ -96,23 +96,7 @@ let Countries = (() => {
|
|
|
96
96
|
return countries.map(c => this.getIsoFromCountry(c, locale));
|
|
97
97
|
}
|
|
98
98
|
async getIsoFromLatLng(point) {
|
|
99
|
-
|
|
100
|
-
const query = `[out:json][timeout:10];is_in(${lat},${lng})->.a;rel(pivot.a)['admin_level'='2']['boundary'='administrative'];out tags;`;
|
|
101
|
-
try {
|
|
102
|
-
const result = await this.getOverpassApi().query(query);
|
|
103
|
-
if (!result?.elements)
|
|
104
|
-
return undefined;
|
|
105
|
-
const isos = result.elements
|
|
106
|
-
.map(el => el.tags['ISO3166-1:alpha2'])
|
|
107
|
-
.filter((iso) => !!iso)
|
|
108
|
-
.map(iso => iso.toUpperCase());
|
|
109
|
-
if (isos.length < 2)
|
|
110
|
-
return isos[0];
|
|
111
|
-
return isos.find(iso => this.getList().countries[iso] !== undefined);
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
return undefined;
|
|
115
|
-
}
|
|
99
|
+
return;
|
|
116
100
|
}
|
|
117
101
|
getOverpassApi() {
|
|
118
102
|
return (0, services_1.useOverpassApi)();
|
|
@@ -443,10 +443,11 @@ let RideDisplayService = (() => {
|
|
|
443
443
|
}
|
|
444
444
|
async stopRide(props = {}) {
|
|
445
445
|
try {
|
|
446
|
-
if (this.state !== 'Starting' && this.state !== 'Idle') {
|
|
447
|
-
this.logEvent({ message: "activity stopped", activity: this.activity?.id, lastState: this.state });
|
|
448
|
-
}
|
|
449
446
|
const prevState = this.state;
|
|
447
|
+
const isRedundantCall = prevState === 'Finished' || prevState === 'Idle' || prevState === 'Closing';
|
|
448
|
+
if (!isRedundantCall && this.activity) {
|
|
449
|
+
this.logEvent({ message: "activity stopped", activity: this.activity?.id, lastState: prevState });
|
|
450
|
+
}
|
|
450
451
|
this.state = prevState === 'Starting' ? 'Closing' : 'Finished';
|
|
451
452
|
this.observer?.stop({ immediately: props.noStateUpdates });
|
|
452
453
|
if (props.noStateUpdates)
|
|
@@ -456,7 +457,7 @@ let RideDisplayService = (() => {
|
|
|
456
457
|
(0, utils_1.waitNextTick)().then(() => { if (this.observer === observerToClear)
|
|
457
458
|
delete this.observer; });
|
|
458
459
|
}
|
|
459
|
-
if (
|
|
460
|
+
if (isRedundantCall) {
|
|
460
461
|
this.state = 'Idle';
|
|
461
462
|
return;
|
|
462
463
|
}
|
|
@@ -140,10 +140,10 @@ let GpxDisplayService = (() => {
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
waitsForStreetView() {
|
|
143
|
-
return this.
|
|
143
|
+
return this.getRideSettingsDisplay().getRideView() === 'sv';
|
|
144
144
|
}
|
|
145
145
|
waitsForSatelliteView() {
|
|
146
|
-
return this.
|
|
146
|
+
return this.getRideSettingsDisplay().getRideView() === 'sat';
|
|
147
147
|
}
|
|
148
148
|
getSatelliteViewProps() {
|
|
149
149
|
return {
|
|
@@ -217,8 +217,8 @@ let GpxDisplayService = (() => {
|
|
|
217
217
|
this.clearSatelliteViewStartTimeout();
|
|
218
218
|
}
|
|
219
219
|
else if (state === 'Error') {
|
|
220
|
-
this.logEvent({ message: 'sat view error', error: this.mapError });
|
|
221
220
|
this.mapError = error;
|
|
221
|
+
this.logEvent({ message: 'sat view error', error: this.mapError });
|
|
222
222
|
}
|
|
223
223
|
this.emit('state-update');
|
|
224
224
|
}
|
|
@@ -131,7 +131,7 @@ let RLVDisplayService = (() => {
|
|
|
131
131
|
}
|
|
132
132
|
await this.checkForAdditionalVideos(videoRoute);
|
|
133
133
|
const nextId = (0, routes_1.getNextVideoId)(videoRoute);
|
|
134
|
-
this.logEvent({ message: 'video added', id: videoRoute.description.id, title: videoRoute.description.title, segmentDistance: videoRoute.description.distance, next: nextId });
|
|
134
|
+
this.logEvent({ message: 'video added', id: videoRoute.description.id, title: videoRoute.description.title, segmentDistance: videoRoute.description.distance, next: nextId, src: video?.source });
|
|
135
135
|
}
|
|
136
136
|
catch (err) {
|
|
137
137
|
this.logError(err, 'addVideo');
|
|
@@ -524,7 +524,7 @@ let RLVDisplayService = (() => {
|
|
|
524
524
|
video.source.getObserver().on('convert-error', (message) => this.onConvertError(message, video));
|
|
525
525
|
}
|
|
526
526
|
cleanupUrl(url) {
|
|
527
|
-
return (0, video_1.resolvePlaybackUrl)(url
|
|
527
|
+
return (0, video_1.resolvePlaybackUrl)(url);
|
|
528
528
|
}
|
|
529
529
|
isLoopEnabled() {
|
|
530
530
|
return this.isLoop() && !this.startSettings?.loopOverwrite;
|
|
@@ -4,16 +4,27 @@ exports.GPXParser = void 0;
|
|
|
4
4
|
const utils_1 = require("../../../utils");
|
|
5
5
|
const math_1 = require("../../../utils/math");
|
|
6
6
|
const valid_1 = require("../../../utils/valid");
|
|
7
|
+
const xml_1 = require("../../../utils/xml");
|
|
7
8
|
const route_1 = require("../utils/route");
|
|
8
|
-
const
|
|
9
|
+
const gpxFast_1 = require("./gpxFast");
|
|
10
|
+
const xml_2 = require("./xml");
|
|
9
11
|
const MIN_DISTANCE = 1;
|
|
10
|
-
class GPXParser extends
|
|
12
|
+
class GPXParser extends xml_2.XMLParser {
|
|
11
13
|
static SCHEME = 'gpx';
|
|
12
14
|
props;
|
|
13
15
|
constructor(props = {}) {
|
|
14
16
|
super();
|
|
15
17
|
this.props = props;
|
|
16
18
|
}
|
|
19
|
+
async getData(file, data) {
|
|
20
|
+
if (data)
|
|
21
|
+
return data;
|
|
22
|
+
const resData = await this.readAndDecode(file);
|
|
23
|
+
const fast = (0, gpxFast_1.tryParseGpxRaw)(resData);
|
|
24
|
+
if (fast)
|
|
25
|
+
return new xml_1.XmlJSON(fast, 'gpx');
|
|
26
|
+
return await (0, xml_1.parseXml)(resData);
|
|
27
|
+
}
|
|
17
28
|
getPrimaryExtension() {
|
|
18
29
|
return 'gpx';
|
|
19
30
|
}
|
|
@@ -125,7 +136,7 @@ class GPXParser extends xml_1.XMLParser {
|
|
|
125
136
|
original: gpxPt,
|
|
126
137
|
parsed: point
|
|
127
138
|
};
|
|
128
|
-
this.
|
|
139
|
+
this.getLogger()?.logEvent({ message: 'error', fn: 'loadPoints', reason: 'unvalid point', info });
|
|
129
140
|
return;
|
|
130
141
|
}
|
|
131
142
|
if (this.props.addTime) {
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tryParseGpxRaw = void 0;
|
|
4
|
+
const decodeEntities = (s) => s.replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll(''', "'").replaceAll('&', '&');
|
|
5
|
+
const isTagWhitespace = (c) => c === ' ' || c === '\t' || c === '\n' || c === '\r';
|
|
6
|
+
const isAttrBoundary = (c) => c === '=' || c === '>' || c === '/' || isTagWhitespace(c);
|
|
7
|
+
const skipBoundary = (s, i) => {
|
|
8
|
+
while (i < s.length && (s[i] === '<' || isAttrBoundary(s[i])))
|
|
9
|
+
i++;
|
|
10
|
+
return i;
|
|
11
|
+
};
|
|
12
|
+
const skipWhitespace = (s, i) => {
|
|
13
|
+
while (i < s.length && isTagWhitespace(s[i]))
|
|
14
|
+
i++;
|
|
15
|
+
return i;
|
|
16
|
+
};
|
|
17
|
+
const readToken = (s, i) => {
|
|
18
|
+
const start = i;
|
|
19
|
+
while (i < s.length && !isAttrBoundary(s[i]))
|
|
20
|
+
i++;
|
|
21
|
+
return { token: s.slice(start, i), next: i };
|
|
22
|
+
};
|
|
23
|
+
const readAttr = (s, i) => {
|
|
24
|
+
const { token: name, next: afterName } = readToken(s, i);
|
|
25
|
+
let j = skipWhitespace(s, afterName);
|
|
26
|
+
if (s[j] !== '=')
|
|
27
|
+
return { name, next: j };
|
|
28
|
+
j = skipWhitespace(s, j + 1);
|
|
29
|
+
const quote = s[j];
|
|
30
|
+
if (quote !== '"' && quote !== "'")
|
|
31
|
+
return { name, next: j };
|
|
32
|
+
const valueStart = j + 1;
|
|
33
|
+
const closeIdx = s.indexOf(quote, valueStart);
|
|
34
|
+
if (closeIdx === -1)
|
|
35
|
+
return { name, next: s.length };
|
|
36
|
+
return { name, value: s.slice(valueStart, closeIdx), next: closeIdx + 1 };
|
|
37
|
+
};
|
|
38
|
+
const parseAttrs = (tagText) => {
|
|
39
|
+
const attrs = {};
|
|
40
|
+
let i = 0;
|
|
41
|
+
while (i < tagText.length) {
|
|
42
|
+
i = skipBoundary(tagText, i);
|
|
43
|
+
if (i >= tagText.length)
|
|
44
|
+
break;
|
|
45
|
+
const { name, value, next } = readAttr(tagText, i);
|
|
46
|
+
if (name && value !== undefined)
|
|
47
|
+
attrs[name] = value;
|
|
48
|
+
i = next;
|
|
49
|
+
}
|
|
50
|
+
return attrs;
|
|
51
|
+
};
|
|
52
|
+
const CDATA_RE = /^<!\[CDATA\[([\s\S]*)\]\]>$/;
|
|
53
|
+
const extractLeaf = (block, tag) => {
|
|
54
|
+
const openTag = `<${tag}`;
|
|
55
|
+
const start = block.indexOf(openTag);
|
|
56
|
+
if (start === -1)
|
|
57
|
+
return undefined;
|
|
58
|
+
const afterName = block[start + 1 + tag.length];
|
|
59
|
+
if (afterName !== undefined && !/[\s>/]/.test(afterName))
|
|
60
|
+
return undefined;
|
|
61
|
+
const tagEnd = block.indexOf('>', start);
|
|
62
|
+
if (tagEnd === -1)
|
|
63
|
+
return undefined;
|
|
64
|
+
if (block[tagEnd - 1] === '/')
|
|
65
|
+
return '';
|
|
66
|
+
const closeTag = `</${tag}>`;
|
|
67
|
+
const closeIdx = block.indexOf(closeTag, tagEnd);
|
|
68
|
+
if (closeIdx === -1)
|
|
69
|
+
return undefined;
|
|
70
|
+
const raw = block.slice(tagEnd + 1, closeIdx).trim();
|
|
71
|
+
const cdata = CDATA_RE.exec(raw);
|
|
72
|
+
return cdata ? cdata[1] : decodeEntities(raw);
|
|
73
|
+
};
|
|
74
|
+
const extractBlocks = (text, tag) => {
|
|
75
|
+
const blocks = [];
|
|
76
|
+
let idx = 0;
|
|
77
|
+
while (true) {
|
|
78
|
+
const start = text.indexOf(`<${tag}`, idx);
|
|
79
|
+
if (start === -1)
|
|
80
|
+
break;
|
|
81
|
+
const afterName = text[start + 1 + tag.length];
|
|
82
|
+
if (afterName !== undefined && !/[\s>/]/.test(afterName)) {
|
|
83
|
+
idx = start + 1;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const tagEnd = text.indexOf('>', start);
|
|
87
|
+
if (tagEnd === -1)
|
|
88
|
+
return undefined;
|
|
89
|
+
if (text[tagEnd - 1] === '/') {
|
|
90
|
+
blocks.push({ body: '' });
|
|
91
|
+
idx = tagEnd + 1;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const closeTag = `</${tag}>`;
|
|
95
|
+
const closeIdx = text.indexOf(closeTag, tagEnd);
|
|
96
|
+
if (closeIdx === -1)
|
|
97
|
+
return undefined;
|
|
98
|
+
blocks.push({ body: text.slice(tagEnd + 1, closeIdx) });
|
|
99
|
+
idx = closeIdx + closeTag.length;
|
|
100
|
+
}
|
|
101
|
+
return blocks;
|
|
102
|
+
};
|
|
103
|
+
const parseOneTrkpt = (segBody, start) => {
|
|
104
|
+
const tagEnd = segBody.indexOf('>', start);
|
|
105
|
+
if (tagEnd === -1)
|
|
106
|
+
return undefined;
|
|
107
|
+
const attrs = parseAttrs(segBody.slice(start, tagEnd + 1));
|
|
108
|
+
const point = { $: { lat: attrs.lat, lon: attrs.lon } };
|
|
109
|
+
if (segBody[tagEnd - 1] === '/')
|
|
110
|
+
return { point, nextIdx: tagEnd + 1 };
|
|
111
|
+
const closeIdx = segBody.indexOf('</trkpt>', tagEnd);
|
|
112
|
+
if (closeIdx === -1)
|
|
113
|
+
return undefined;
|
|
114
|
+
const body = segBody.slice(tagEnd + 1, closeIdx);
|
|
115
|
+
const ele = extractLeaf(body, 'ele');
|
|
116
|
+
if (ele !== undefined)
|
|
117
|
+
point.ele = [ele];
|
|
118
|
+
const time = extractLeaf(body, 'time');
|
|
119
|
+
if (time !== undefined)
|
|
120
|
+
point.time = [time];
|
|
121
|
+
return { point, nextIdx: closeIdx + '</trkpt>'.length };
|
|
122
|
+
};
|
|
123
|
+
const extractTrkpts = (segBody) => {
|
|
124
|
+
const points = [];
|
|
125
|
+
let idx = 0;
|
|
126
|
+
while (true) {
|
|
127
|
+
const start = segBody.indexOf('<trkpt', idx);
|
|
128
|
+
if (start === -1)
|
|
129
|
+
break;
|
|
130
|
+
const afterName = segBody[start + 6];
|
|
131
|
+
if (afterName !== undefined && !/[\s>/]/.test(afterName)) {
|
|
132
|
+
idx = start + 1;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const parsed = parseOneTrkpt(segBody, start);
|
|
136
|
+
if (!parsed)
|
|
137
|
+
return undefined;
|
|
138
|
+
points.push(parsed.point);
|
|
139
|
+
idx = parsed.nextIdx;
|
|
140
|
+
}
|
|
141
|
+
return points;
|
|
142
|
+
};
|
|
143
|
+
const buildTrk = (body) => {
|
|
144
|
+
const segStart = body.search(/<trkseg[\s>]/);
|
|
145
|
+
const header = segStart === -1 ? body : body.slice(0, segStart);
|
|
146
|
+
const segBlocks = extractBlocks(body, 'trkseg');
|
|
147
|
+
if (!segBlocks)
|
|
148
|
+
return undefined;
|
|
149
|
+
const trkseg = [];
|
|
150
|
+
for (const seg of segBlocks) {
|
|
151
|
+
const points = extractTrkpts(seg.body);
|
|
152
|
+
if (!points)
|
|
153
|
+
return undefined;
|
|
154
|
+
trkseg.push({ trkpt: points });
|
|
155
|
+
}
|
|
156
|
+
const trkObj = { trkseg };
|
|
157
|
+
const name = extractLeaf(header, 'name');
|
|
158
|
+
if (name !== undefined)
|
|
159
|
+
trkObj.name = [name];
|
|
160
|
+
const desc = extractLeaf(header, 'desc');
|
|
161
|
+
if (desc !== undefined)
|
|
162
|
+
trkObj.desc = [desc];
|
|
163
|
+
return trkObj;
|
|
164
|
+
};
|
|
165
|
+
const buildMetadata = (xmlText) => {
|
|
166
|
+
const metaMatch = /<metadata[\s>][\s\S]*?<\/metadata>/.exec(xmlText);
|
|
167
|
+
if (!metaMatch)
|
|
168
|
+
return undefined;
|
|
169
|
+
const openEnd = metaMatch[0].indexOf('>');
|
|
170
|
+
const closeStart = metaMatch[0].lastIndexOf('</metadata>');
|
|
171
|
+
const body = metaMatch[0].slice(openEnd + 1, closeStart).replace(/<author[\s>][\s\S]*?<\/author>/, '');
|
|
172
|
+
const name = extractLeaf(body, 'name');
|
|
173
|
+
return name !== undefined ? { name: [name] } : undefined;
|
|
174
|
+
};
|
|
175
|
+
const countPoints = (trk) => trk.reduce((sum, t) => sum + t.trkseg.reduce((s, seg) => s + seg.trkpt.length, 0), 0);
|
|
176
|
+
const tryParseGpxRaw = (xmlText) => {
|
|
177
|
+
try {
|
|
178
|
+
if (!/<gpx[\s>]/.test(xmlText))
|
|
179
|
+
return undefined;
|
|
180
|
+
const trkBlocks = extractBlocks(xmlText, 'trk');
|
|
181
|
+
if (!trkBlocks || trkBlocks.length === 0)
|
|
182
|
+
return undefined;
|
|
183
|
+
const trk = [];
|
|
184
|
+
for (const { body } of trkBlocks) {
|
|
185
|
+
const trkObj = buildTrk(body);
|
|
186
|
+
if (!trkObj)
|
|
187
|
+
return undefined;
|
|
188
|
+
trk.push(trkObj);
|
|
189
|
+
}
|
|
190
|
+
const gpx = { trk };
|
|
191
|
+
const metadata = buildMetadata(xmlText);
|
|
192
|
+
if (metadata)
|
|
193
|
+
gpx.metadata = [metadata];
|
|
194
|
+
const literalCount = (xmlText.match(/<trkpt[\s>]/g) ?? []).length;
|
|
195
|
+
if (countPoints(trk) !== literalCount)
|
|
196
|
+
return undefined;
|
|
197
|
+
return { gpx };
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
exports.tryParseGpxRaw = tryParseGpxRaw;
|
|
@@ -49,7 +49,8 @@ class IncyclistXMLParser extends xml_1.XMLParser {
|
|
|
49
49
|
gpxFile.url = gpxFile.url.replace(xmlBase, fileName);
|
|
50
50
|
}
|
|
51
51
|
else {
|
|
52
|
-
|
|
52
|
+
const encodedFileName = fileInfo.delimiter === '%2F' ? encodeURIComponent(fileName) : fileName;
|
|
53
|
+
gpxFile.filename = `${fileInfo.dir}${fileInfo.delimiter}${encodedFileName}`;
|
|
53
54
|
}
|
|
54
55
|
try {
|
|
55
56
|
let points;
|
|
@@ -142,8 +142,8 @@ const safeEncode = (path) => {
|
|
|
142
142
|
};
|
|
143
143
|
exports.safeEncode = safeEncode;
|
|
144
144
|
const handleFileUrlPath = (fileName) => {
|
|
145
|
-
const cleanPath = (0, exports.safeDecode)(fileName.replace(/^file
|
|
146
|
-
return
|
|
145
|
+
const cleanPath = (0, exports.safeDecode)(fileName.replace(/^file:\/\//, ''));
|
|
146
|
+
return (0, exports.buildFileUrl)(cleanPath);
|
|
147
147
|
};
|
|
148
148
|
const buildFileUrl = (normalizedPath) => {
|
|
149
149
|
if (isAbsolutePath(normalizedPath)) {
|
|
@@ -231,26 +231,31 @@ const decodeUtf16Be = (data) => {
|
|
|
231
231
|
return Buffer.from(swapped.toString('utf16le')).toString('utf-8');
|
|
232
232
|
};
|
|
233
233
|
const getUtf8Data = (res) => {
|
|
234
|
-
const
|
|
235
|
-
if (
|
|
234
|
+
const prefix = Buffer.isBuffer(res) ? res.subarray(0, 4) : Buffer.from(res.slice(0, 4), 'binary');
|
|
235
|
+
if (prefix[0] === 0xFE && prefix[1] === 0xFF) {
|
|
236
|
+
const buf = Buffer.isBuffer(res) ? res : Buffer.from(res, 'binary');
|
|
236
237
|
return decodeUtf16Be(buf.subarray(2));
|
|
237
238
|
}
|
|
238
|
-
if (
|
|
239
|
+
if (prefix[0] === 0xFF && prefix[1] === 0xFE) {
|
|
240
|
+
const buf = Buffer.isBuffer(res) ? res : Buffer.from(res, 'binary');
|
|
239
241
|
return Buffer.from(buf.subarray(2).toString('utf16le')).toString('utf-8');
|
|
240
242
|
}
|
|
241
|
-
if (
|
|
242
|
-
|
|
243
|
+
if (prefix[0] === 0xFD && prefix[1] === 0xFD) {
|
|
244
|
+
const buf = Buffer.isBuffer(res) ? res : Buffer.from(res, 'binary');
|
|
245
|
+
if (prefix[2] === 0x00 && prefix[3] === 0x3C) {
|
|
243
246
|
return decodeUtf16Be(buf.subarray(2));
|
|
244
247
|
}
|
|
245
|
-
if (
|
|
248
|
+
if (prefix[2] === 0x3C && prefix[3] === 0x00) {
|
|
246
249
|
return Buffer.from(buf.subarray(2).toString('utf16le')).toString('utf-8');
|
|
247
250
|
}
|
|
248
251
|
}
|
|
249
|
-
const str = typeof res === 'string' ? res :
|
|
252
|
+
const str = typeof res === 'string' ? res : res.toString('utf-8');
|
|
250
253
|
if (str.codePointAt(0) === 0xFEFF)
|
|
251
254
|
return str.slice(1);
|
|
252
|
-
if (
|
|
255
|
+
if (prefix[0] === 0xEF && prefix[1] === 0xBB && prefix[2] === 0xBF) {
|
|
256
|
+
const buf = Buffer.isBuffer(res) ? res : Buffer.from(res, 'binary');
|
|
253
257
|
return buf.subarray(3).toString('utf-8');
|
|
258
|
+
}
|
|
254
259
|
return str;
|
|
255
260
|
};
|
|
256
261
|
exports.getUtf8Data = getUtf8Data;
|
|
@@ -35,6 +35,10 @@ class XMLParser {
|
|
|
35
35
|
async getData(file, data) {
|
|
36
36
|
if (data)
|
|
37
37
|
return data;
|
|
38
|
+
const resData = await this.readAndDecode(file);
|
|
39
|
+
return await (0, xml_1.parseXml)(resData);
|
|
40
|
+
}
|
|
41
|
+
async readAndDecode(file) {
|
|
38
42
|
const onError = () => {
|
|
39
43
|
throw new Error('Could not open file: ' + (0, utils_2.getFileName)(file));
|
|
40
44
|
};
|
|
@@ -45,9 +49,7 @@ class XMLParser {
|
|
|
45
49
|
this.getLogger().logEvent({ message: '[Parser] getData error', error: res.error });
|
|
46
50
|
onError();
|
|
47
51
|
}
|
|
48
|
-
|
|
49
|
-
const xml = await (0, xml_1.parseXml)(resData);
|
|
50
|
-
return xml;
|
|
52
|
+
return (0, utils_1.getUtf8Data)(res.data);
|
|
51
53
|
}
|
|
52
54
|
catch (err) {
|
|
53
55
|
this.getLogger().logEvent({ message: '[Parser] getData error', error: err.message });
|
|
@@ -278,6 +280,14 @@ class XMLParser {
|
|
|
278
280
|
let prevDistance = 0;
|
|
279
281
|
route.elevation = 0;
|
|
280
282
|
const points = [];
|
|
283
|
+
const altitudeByDistance = new Map();
|
|
284
|
+
if (Array.isArray(altitudes)) {
|
|
285
|
+
altitudes.forEach(a => {
|
|
286
|
+
const d = Number.parseInt(a.distance);
|
|
287
|
+
if (!altitudeByDistance.has(d))
|
|
288
|
+
altitudeByDistance.set(d, a.height);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
281
291
|
positions.forEach((pos, i) => {
|
|
282
292
|
if (i > 0) {
|
|
283
293
|
const prev = positions[i - 1];
|
|
@@ -285,7 +295,7 @@ class XMLParser {
|
|
|
285
295
|
return;
|
|
286
296
|
}
|
|
287
297
|
}
|
|
288
|
-
const altitude = getAltitude(altitudes, positions, i, prevAltitude);
|
|
298
|
+
const altitude = getAltitude(altitudes, altitudeByDistance, positions, i, prevAltitude);
|
|
289
299
|
const elevationGain = altitude - prevAltitude;
|
|
290
300
|
const pi = createPoint(pos, altitude, prevDistance);
|
|
291
301
|
pi.point.cnt = i;
|
|
@@ -342,7 +352,7 @@ function createPoint(pos, altitude, prevDistance) {
|
|
|
342
352
|
}
|
|
343
353
|
return { point, prevDistance };
|
|
344
354
|
}
|
|
345
|
-
function getAltitude(altitudes, positions, i, prevAltitude) {
|
|
355
|
+
function getAltitude(altitudes, altitudeByDistance, positions, i, prevAltitude) {
|
|
346
356
|
const pos = positions[i];
|
|
347
357
|
let height = altitudes[i]?.height;
|
|
348
358
|
if (altitudes && Array.isArray(altitudes)) {
|
|
@@ -350,8 +360,7 @@ function getAltitude(altitudes, positions, i, prevAltitude) {
|
|
|
350
360
|
return height !== undefined ? Number(height) : prevAltitude;
|
|
351
361
|
}
|
|
352
362
|
else {
|
|
353
|
-
|
|
354
|
-
height = altFound?.height;
|
|
363
|
+
height = altitudeByDistance.get(Number.parseInt(pos.distance));
|
|
355
364
|
return height !== undefined ? Number(height) : prevAltitude;
|
|
356
365
|
}
|
|
357
366
|
}
|
|
@@ -371,6 +371,7 @@ let RouteLibraryScannerService = (() => {
|
|
|
371
371
|
const file = this.buildFileInfo(target.controlFileUri, target.format);
|
|
372
372
|
this.importProps.routes.push({
|
|
373
373
|
format: target.format,
|
|
374
|
+
parseState: 'waiting',
|
|
374
375
|
importable: false,
|
|
375
376
|
label: file.base,
|
|
376
377
|
id: target.controlFileUri,
|
|
@@ -401,8 +402,12 @@ let RouteLibraryScannerService = (() => {
|
|
|
401
402
|
async _parseTarget(target, service, observer) {
|
|
402
403
|
let result;
|
|
403
404
|
const file = this.buildFileInfo(target.controlFileUri, target.format);
|
|
405
|
+
const importProps = this.importProps.routes.find(r => r.id === target.controlFileUri) ?? {};
|
|
404
406
|
try {
|
|
407
|
+
importProps.parseState = 'parsing';
|
|
408
|
+
observer.emit('updated', importProps);
|
|
405
409
|
result = await parsers_1.RouteParser.parse(file);
|
|
410
|
+
importProps.parseState = 'parsed';
|
|
406
411
|
const route = new route_1.Route(result.data, result.details);
|
|
407
412
|
const existing = this.importProps.routes.find(ri => ri.id === route.description.id);
|
|
408
413
|
if (existing) {
|
|
@@ -425,6 +430,7 @@ let RouteLibraryScannerService = (() => {
|
|
|
425
430
|
observer.emit('parse-result', parsed);
|
|
426
431
|
}
|
|
427
432
|
catch (err) {
|
|
433
|
+
importProps.parseState = 'parsed';
|
|
428
434
|
const parsed = {
|
|
429
435
|
alreadyImported: false,
|
|
430
436
|
route: result ? new route_1.Route(result.data, result.details) : undefined,
|
|
@@ -581,6 +587,9 @@ let RouteLibraryScannerService = (() => {
|
|
|
581
587
|
delimiter: uri?.startsWith('content://') ? '%2F' : '/'
|
|
582
588
|
};
|
|
583
589
|
}
|
|
590
|
+
getImportProps(parsed) {
|
|
591
|
+
return this.importProps.routes.find(r => r.id === parsed.controlFileUri);
|
|
592
|
+
}
|
|
584
593
|
buildRouteDisplayItem(parsed, observer) {
|
|
585
594
|
const { route, alreadyImported, parseError, format, controlFileUri } = parsed;
|
|
586
595
|
const descr = route?.description ?? {};
|
|
@@ -591,11 +600,13 @@ let RouteLibraryScannerService = (() => {
|
|
|
591
600
|
};
|
|
592
601
|
const path = this.getBindings().path;
|
|
593
602
|
const info = path.parse(controlFileUri);
|
|
603
|
+
const importProps = this.getImportProps(parsed);
|
|
594
604
|
return {
|
|
595
605
|
id: route?.description?.id ?? info?.base,
|
|
596
606
|
distance,
|
|
597
607
|
label: route?.title ?? info?.base,
|
|
598
608
|
alreadyImported,
|
|
609
|
+
parseState: importProps?.parseState ?? 'waiting',
|
|
599
610
|
importable: parseError == null,
|
|
600
611
|
format,
|
|
601
612
|
errorReason: parseError,
|
|
@@ -33,12 +33,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
33
33
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
34
34
|
done = true;
|
|
35
35
|
};
|
|
36
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
-
};
|
|
39
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
37
|
exports.useOverpassApi = exports.OverpassApi = exports.OVERPASS_URL_DEFAULT = void 0;
|
|
41
|
-
const promise_any_1 = __importDefault(require("promise.any"));
|
|
42
38
|
const types_1 = require("../../base/types");
|
|
43
39
|
const sleep_1 = require("../../utils/sleep");
|
|
44
40
|
const base_1 = require("../../apps/base/api/base");
|
|
@@ -47,9 +43,10 @@ const decorators_1 = require("../../base/decorators");
|
|
|
47
43
|
const api_1 = require("../../api");
|
|
48
44
|
const settings_1 = require("../../settings");
|
|
49
45
|
const OVERPASS_URL_PRIMARY = 'https://overpass-api.de/api/interpreter';
|
|
50
|
-
const
|
|
51
|
-
const
|
|
46
|
+
const OVERPASS_URL_ALT2 = 'https://lz4.overpass-api.de/api/interpreter';
|
|
47
|
+
const OVERPASS_URL_ALT3 = 'https://maps.mail.ru/osm/tools/overpass/api/interpreter';
|
|
52
48
|
exports.OVERPASS_URL_DEFAULT = OVERPASS_URL_PRIMARY;
|
|
49
|
+
const DEFAULT_MIRRORS = [OVERPASS_URL_PRIMARY, OVERPASS_URL_ALT2, OVERPASS_URL_ALT3];
|
|
53
50
|
let OverpassApi = (() => {
|
|
54
51
|
let _classDecorators = [types_1.Singleton];
|
|
55
52
|
let _classDescriptor;
|
|
@@ -77,7 +74,7 @@ let OverpassApi = (() => {
|
|
|
77
74
|
logger;
|
|
78
75
|
constructor(props) {
|
|
79
76
|
super();
|
|
80
|
-
this.mirrors =
|
|
77
|
+
this.mirrors = DEFAULT_MIRRORS;
|
|
81
78
|
this.url = props?.url ?? this.mirrors[0];
|
|
82
79
|
this.logger = new gd_eventlog_1.EventLogger('Overpass');
|
|
83
80
|
if (props?.url) {
|
|
@@ -128,25 +125,19 @@ let OverpassApi = (() => {
|
|
|
128
125
|
async postViaFetchBinding(fetchBinding, url, data) {
|
|
129
126
|
const headers = this.getOverpassHeaders();
|
|
130
127
|
const body = typeof data === 'string' ? data : JSON.stringify(data);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
catch (err) {
|
|
147
|
-
this.logger.logEvent({ message: 'fetch error', url, data, error: err.message });
|
|
148
|
-
throw err;
|
|
149
|
-
}
|
|
128
|
+
const res = await fetchBinding.fetch(url, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers,
|
|
131
|
+
body,
|
|
132
|
+
referrerPolicy: 'unsafe-url'
|
|
133
|
+
});
|
|
134
|
+
return {
|
|
135
|
+
data: res.data,
|
|
136
|
+
status: res.status,
|
|
137
|
+
statusText: res.statusText,
|
|
138
|
+
headers: res.headers,
|
|
139
|
+
config: {}
|
|
140
|
+
};
|
|
150
141
|
}
|
|
151
142
|
async postViaNodeHttps(https, url, data) {
|
|
152
143
|
return new Promise((resolve, reject) => {
|
|
@@ -245,10 +236,12 @@ let OverpassApi = (() => {
|
|
|
245
236
|
}));
|
|
246
237
|
});
|
|
247
238
|
if (timeout !== undefined && timeout !== null) {
|
|
248
|
-
promises.push((0, sleep_1.sleep)(timeout).then(() =>
|
|
239
|
+
promises.push((0, sleep_1.sleep)(timeout).then(() => {
|
|
240
|
+
return 'timeout';
|
|
241
|
+
}));
|
|
249
242
|
}
|
|
250
243
|
try {
|
|
251
|
-
const res = await
|
|
244
|
+
const res = await Promise.any(promises);
|
|
252
245
|
if (res === 'timeout') {
|
|
253
246
|
return undefined;
|
|
254
247
|
}
|
|
@@ -289,7 +282,7 @@ let OverpassApi = (() => {
|
|
|
289
282
|
throw lastError;
|
|
290
283
|
}
|
|
291
284
|
reset() {
|
|
292
|
-
this.mirrors =
|
|
285
|
+
this.mirrors = DEFAULT_MIRRORS;
|
|
293
286
|
this.url = this.mirrors[0];
|
|
294
287
|
}
|
|
295
288
|
getAppVersion() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.resolvePlaybackUrl = void 0;
|
|
4
|
-
const resolvePlaybackUrl = (url
|
|
4
|
+
const resolvePlaybackUrl = (url) => {
|
|
5
5
|
if (!url)
|
|
6
6
|
return undefined;
|
|
7
7
|
let fileName = url;
|
|
@@ -10,8 +10,6 @@ const resolvePlaybackUrl = (url, isMobile = false) => {
|
|
|
10
10
|
fileName = fileName.replace('incyclist:', 'file:');
|
|
11
11
|
if (fileName.startsWith('video:') && !lc.endsWith('.avi'))
|
|
12
12
|
return fileName.replace('video:', 'file:');
|
|
13
|
-
if (fileName.startsWith('file:') && !lc.endsWith('.avi') && !isMobile)
|
|
14
|
-
return fileName.replace('file:', 'video:');
|
|
15
13
|
if (fileName.startsWith('video:') && lc.endsWith('.avi'))
|
|
16
14
|
return fileName;
|
|
17
15
|
if (fileName.startsWith('file:') || fileName.startsWith('http:') || fileName.startsWith('https:') || fileName.startsWith('/'))
|
|
@@ -90,23 +90,7 @@ let Countries = (() => {
|
|
|
90
90
|
return countries.map(c => this.getIsoFromCountry(c, locale));
|
|
91
91
|
}
|
|
92
92
|
async getIsoFromLatLng(point) {
|
|
93
|
-
|
|
94
|
-
const query = `[out:json][timeout:10];is_in(${lat},${lng})->.a;rel(pivot.a)['admin_level'='2']['boundary'='administrative'];out tags;`;
|
|
95
|
-
try {
|
|
96
|
-
const result = await this.getOverpassApi().query(query);
|
|
97
|
-
if (!result?.elements)
|
|
98
|
-
return undefined;
|
|
99
|
-
const isos = result.elements
|
|
100
|
-
.map(el => el.tags['ISO3166-1:alpha2'])
|
|
101
|
-
.filter((iso) => !!iso)
|
|
102
|
-
.map(iso => iso.toUpperCase());
|
|
103
|
-
if (isos.length < 2)
|
|
104
|
-
return isos[0];
|
|
105
|
-
return isos.find(iso => this.getList().countries[iso] !== undefined);
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
return undefined;
|
|
109
|
-
}
|
|
93
|
+
return;
|
|
110
94
|
}
|
|
111
95
|
getOverpassApi() {
|
|
112
96
|
return useOverpassApi();
|