incyclist-services 1.3.19 → 1.3.21

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.
@@ -0,0 +1,1194 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.useMapArea = exports.destinationPoint = exports.alongTrackDistanceTo = exports.initialBearingTo = exports.isWithinBoundary = exports.getBounds = exports.getVector = exports.getCrossingInfo = exports.getPointCrossingPath = exports._getPointOnLine = exports.findAdditional = exports.getFirstBranch = exports.isOneWay = exports.isAllowed = exports.isNode = exports.isWay = exports.pointEquals = exports.splitAtPoint = exports.splitAtIndex = exports.isRoundabout = exports.getUntilFirstBranch = exports.splitAtPointInfo = void 0;
13
+ const { EventLogger } = require('gd-eventlog');
14
+ const DEFAULT_RADIUS = 1000;
15
+ const DEFAULT_MIN_WAYS = 70;
16
+ const DEFAULT_MAX_WAYS = 300;
17
+ const MAX_DISTANCE_FROM_PATH = 5;
18
+ const GET_WAYS_IN_AREA = '[out:json];way[highway](__boundary__);(._;>;);out geom;';
19
+ class MapAreaService {
20
+ static getInstance(id = 'default') {
21
+ if (!MapAreaService._instances[id])
22
+ MapAreaService._instances[id] = new MapAreaService();
23
+ return MapAreaService._instances[id];
24
+ }
25
+ constructor(props) {
26
+ this.overpass = new OverpassApi();
27
+ this.logger = new EventLogger('MapArea');
28
+ this.init(props);
29
+ }
30
+ init(props) {
31
+ this.minWays = DEFAULT_MIN_WAYS;
32
+ this.maxWays = DEFAULT_MAX_WAYS;
33
+ this.radius = DEFAULT_RADIUS;
34
+ this.nodesLookup = [];
35
+ this.waysLookup = [];
36
+ this.ways = [];
37
+ if (props === undefined)
38
+ return;
39
+ if (props.onLoaded)
40
+ this.onLoaded = props.onLoaded;
41
+ if (props.minWays)
42
+ this.minWays = props.minWays;
43
+ if (props.maxWays)
44
+ this.maxWays = props.maxWays;
45
+ if (props.radius)
46
+ this.radius = props.radius;
47
+ if (props.filter)
48
+ this.filter = props.filter;
49
+ if (props.location) {
50
+ this.setLocation(props.location, true, this.onLoaded);
51
+ }
52
+ }
53
+ isInitialized() {
54
+ return this.boundary !== undefined;
55
+ }
56
+ load(callback) {
57
+ return __awaiter(this, void 0, void 0, function* () {
58
+ const onLoaded = callback ? callback : this.onLoaded;
59
+ let ts, ts1;
60
+ try {
61
+ this.query = this._buildQuery(GET_WAYS_IN_AREA);
62
+ this.queryLocation = this.location;
63
+ const { id, lat, lng } = this.location;
64
+ this.logger.logEvent({ message: 'overpass query', query: this.query, location: { id, lat, lng }, radius: this.radius });
65
+ ts = Date.now();
66
+ let openmapData = yield this.overpass.query(this.query);
67
+ ts1 = Date.now();
68
+ this.logger.logEvent({ message: 'overpass query result', status: 'success', duration: (ts1 - ts) });
69
+ this.setData(openmapData, onLoaded);
70
+ }
71
+ catch (error) {
72
+ ts1 = Date.now();
73
+ this.logger.logEvent({ message: 'overpass query result', status: 'failure', error: { code: error.code, response: error.response }, duration: (ts1 - ts) });
74
+ if (onLoaded !== undefined)
75
+ onLoaded('failure', this.ways, this.openmapData);
76
+ }
77
+ });
78
+ }
79
+ setData(openmapData, callback) {
80
+ const onLoaded = callback ? callback : this.onLoaded;
81
+ let ts = Date.now();
82
+ let res = MapAreaService._parse(openmapData, this.filter);
83
+ let ts1 = Date.now();
84
+ this.logger.logEvent({ message: 'Parse', duration: (ts1 - ts),
85
+ ways: (res !== undefined && res.ways !== undefined ? res.ways.length : 0),
86
+ nodes: (res !== undefined && res.nodes !== undefined ? res.nodes.length : 0),
87
+ typeStats: (res !== undefined ? res.typeStats : '')
88
+ });
89
+ if (res !== undefined) {
90
+ this.openmapData = openmapData;
91
+ this.ways = res.ways;
92
+ this.nodesLookup = res.nodesLookup;
93
+ this.waysLookup = res.waysLookup;
94
+ this._checkRoundabouts();
95
+ this.loaded = 'success';
96
+ if (this.ways.length < this.minWays) {
97
+ if (this.ways.length > 0) {
98
+ let gap = this.minWays / this.ways.length;
99
+ this.radius = this.radius * Math.sqrt(gap);
100
+ }
101
+ else {
102
+ this.radius = this.radius * 2;
103
+ }
104
+ }
105
+ else if (this.ways.length > this.maxWays) {
106
+ let gap = this.ways.length / this.maxWays;
107
+ this.radius = this.radius / Math.sqrt(gap);
108
+ }
109
+ }
110
+ else {
111
+ this.loaded = 'failure';
112
+ }
113
+ if (onLoaded !== undefined) {
114
+ onLoaded(this.loaded, this.ways, this.openmapData);
115
+ }
116
+ }
117
+ _collectRoundabout(way) {
118
+ if (!isRoundabout(way, true))
119
+ return undefined;
120
+ const contains = (ways, wid) => {
121
+ let found = ways.find(id => id === wid);
122
+ return found !== undefined;
123
+ };
124
+ const addNodes = (ways, way) => {
125
+ way.path.forEach((n, idx) => {
126
+ if (idx > 0) {
127
+ n.ways.forEach(wid => {
128
+ let w = this.waysLookup[wid];
129
+ if (isRoundabout(w, true) && wid !== way.id && !contains(ways, wid)) {
130
+ ways.push(wid);
131
+ addNodes(ways, w);
132
+ }
133
+ });
134
+ }
135
+ });
136
+ };
137
+ let ways = [way.id];
138
+ addNodes(ways, way);
139
+ return ways;
140
+ }
141
+ ;
142
+ static _generateID(ways) {
143
+ if (ways === undefined || ways.length === 0)
144
+ return;
145
+ if (ways.length < 2)
146
+ return ways[0];
147
+ let w = [...ways];
148
+ let widStr = 'R:' + w.sort().join(',');
149
+ return widStr;
150
+ }
151
+ ;
152
+ _replaceWayID(way, newId, replaceLookup = true) {
153
+ if (way === undefined || newId === undefined || way.id === undefined || way.path === undefined)
154
+ return;
155
+ let oldId = way.id;
156
+ let w = this.waysLookup[oldId];
157
+ let self = this;
158
+ w.originalId = w.id;
159
+ w.id = newId;
160
+ w.path.forEach((nx, j) => {
161
+ w.path[j] = self.nodesLookup[nx.id];
162
+ let n = w.path[j];
163
+ n.ways.forEach((wid, i) => { if (wid === oldId)
164
+ n.ways[i] = newId; });
165
+ n.ways = [...new Set(n.ways)];
166
+ });
167
+ if (replaceLookup)
168
+ this.waysLookup[newId] = w;
169
+ delete this.waysLookup[oldId];
170
+ return w;
171
+ }
172
+ ;
173
+ _checkRoundabouts() {
174
+ let roundaboutsStrict = [];
175
+ let roundaboutsImplicit = [];
176
+ let self = this;
177
+ try {
178
+ this.ways.forEach(way => {
179
+ if (isRoundabout(way, true)) {
180
+ let ways = self._collectRoundabout(way);
181
+ let id = MapAreaService._generateID(ways);
182
+ let found = roundaboutsStrict.find(e => e.id === id);
183
+ if (!found) {
184
+ found = (way.path[0].id === way.path[way.path.length - 1].id);
185
+ if (found) {
186
+ roundaboutsImplicit.push(way);
187
+ }
188
+ }
189
+ if (!found)
190
+ roundaboutsStrict.push({ id, ways });
191
+ }
192
+ else if (isRoundabout(way, false)) {
193
+ roundaboutsImplicit.push(way);
194
+ }
195
+ });
196
+ this.logger.debug('_checkRoundabouts: found roundabouts:', roundaboutsStrict);
197
+ roundaboutsStrict.forEach(ri => {
198
+ let originalNodes = [];
199
+ ri.ways.forEach((wid, i) => {
200
+ let way = self.waysLookup[wid];
201
+ let path = way.path;
202
+ path.forEach(n => {
203
+ if (originalNodes.length === 0 ||
204
+ originalNodes[originalNodes.length - 1] !== n.id)
205
+ originalNodes.push(n.id);
206
+ });
207
+ });
208
+ ri.ways.forEach((wid, i) => {
209
+ let way = self.waysLookup[wid];
210
+ self._replaceWayID(way, ri.id, i === 0);
211
+ });
212
+ let roundabout = self.waysLookup[ri.id];
213
+ roundabout.path = [];
214
+ originalNodes.forEach(nid => {
215
+ let node = self.nodesLookup[nid];
216
+ roundabout.path.push(node);
217
+ });
218
+ roundabout.tags.roundabout = true;
219
+ });
220
+ roundaboutsImplicit.forEach(roundabout => {
221
+ if (roundabout.tags === undefined)
222
+ roundabout.tags = {};
223
+ roundabout.tags.roundabout = true;
224
+ });
225
+ }
226
+ catch (error) {
227
+ this.logger.logEvent({ message: 'Error', error });
228
+ }
229
+ }
230
+ setLocation(location, reload, onLoaded) {
231
+ this.location = location;
232
+ if (location === undefined) {
233
+ if (onLoaded)
234
+ onLoaded('failure', this.ways, this.openmapData);
235
+ return;
236
+ }
237
+ if (reload) {
238
+ this.boundary = MapAreaService._getBoundary(this.location, this.radius);
239
+ this.load(onLoaded);
240
+ this.center = this.location;
241
+ }
242
+ else {
243
+ if (onLoaded)
244
+ onLoaded('success', this.ways, this.openmapData);
245
+ }
246
+ }
247
+ _buildQuery(template) {
248
+ if (template === undefined)
249
+ return;
250
+ return template.replace('__boundary__', MapAreaService._boundaryToString(this.boundary));
251
+ }
252
+ getNearestPath(point) {
253
+ return MapAreaService._getNearestPath(point, this.ways);
254
+ }
255
+ static _getNearestPath(point, ways) {
256
+ if (ways === undefined || point === undefined)
257
+ return;
258
+ let w;
259
+ let min = { path: undefined, distance: undefined, way: undefined };
260
+ for (w of ways) {
261
+ let distance = MapAreaService._distanceToPath(point, w);
262
+ if (distance !== undefined && (min.distance === undefined || distance < min.distance)) {
263
+ min.distance = distance;
264
+ min.path = w.path;
265
+ min.way = w;
266
+ }
267
+ }
268
+ return min;
269
+ }
270
+ static _isWithinRange(distance) {
271
+ return abs(distance) < MAX_DISTANCE_FROM_PATH;
272
+ }
273
+ static _isOnPath(point, way) {
274
+ if (point === undefined || point.lat === undefined || point.lng === undefined
275
+ || way === undefined || way.path === undefined || way.path.length === 0)
276
+ return false;
277
+ return MapAreaService._distanceToPath(point, way) < MAX_DISTANCE_FROM_PATH;
278
+ }
279
+ static _isBetween(point, p1, p2) {
280
+ if (point === undefined || p1 === undefined || p2 === undefined ||
281
+ point.lat === undefined || point.lng === undefined ||
282
+ p1.lat === undefined || p1.lng === undefined ||
283
+ p2.lat === undefined || p2.lng === undefined)
284
+ return undefined;
285
+ let d = geo.distanceBetween(p1, point);
286
+ let bDest = initialBearingTo(p1, p2);
287
+ let bPoint = initialBearingTo(p1, point);
288
+ let angle = bPoint - bDest;
289
+ if (abs(angle) > 30)
290
+ return { between: false, offset: d };
291
+ let dP2 = geo.distanceBetween(p1, p2);
292
+ let offset = abs(d * sin(angle));
293
+ if (offset < MAX_DISTANCE_FROM_PATH && dP2 > d) {
294
+ return { between: true, offset };
295
+ }
296
+ return { between: false, offset: (dP2 - d) };
297
+ }
298
+ static _distanceToPath(point, way) {
299
+ if (point === undefined || point.lat === undefined || point.lng === undefined
300
+ || way === undefined || way.path === undefined || way.path.length === 0)
301
+ return undefined;
302
+ let minDistance;
303
+ let minIdx = -1;
304
+ let prev;
305
+ let foundExact = false;
306
+ way.path.forEach((element, idx) => {
307
+ if (element.lat === point.lat && element.lng === point.lng) {
308
+ minDistance = 0;
309
+ foundExact = true;
310
+ return;
311
+ }
312
+ if (foundExact)
313
+ return;
314
+ if (prev !== undefined) {
315
+ let res = MapAreaService._isBetween(point, prev, element);
316
+ if (res && res.between) {
317
+ minDistance = res.offset;
318
+ foundExact = true;
319
+ return;
320
+ }
321
+ }
322
+ let distance = geo.distanceBetween(element, point);
323
+ if (minDistance === undefined || distance < minDistance) {
324
+ minDistance = distance;
325
+ minIdx = idx;
326
+ }
327
+ prev = element;
328
+ });
329
+ if (foundExact)
330
+ return minDistance;
331
+ if (minIdx > 0) {
332
+ const alpha = initialBearingTo(way.path[minIdx - 1], way.path[minIdx]);
333
+ const alpha2 = initialBearingTo(way.path[minIdx - 1], point);
334
+ let beta = alpha2 - alpha;
335
+ if (beta > 270)
336
+ beta -= 360;
337
+ if ((beta > -90 && beta < 90)) {
338
+ const dist = sin(abs(beta)) * geo.distanceBetween(way.path[minIdx - 1], point);
339
+ const distPath = cos(abs(beta)) * geo.distanceBetween(way.path[minIdx - 1], point);
340
+ if (dist < minDistance && distPath > 0 && distPath < geo.distanceBetween(way.path[minIdx - 1], way.path[minIdx]))
341
+ minDistance = dist;
342
+ }
343
+ }
344
+ if (minIdx < way.path.length - 2) {
345
+ const alpha = initialBearingTo(way.path[minIdx], way.path[minIdx + 1]);
346
+ let beta = initialBearingTo(way.path[minIdx], point) - alpha;
347
+ if (beta > 270)
348
+ beta -= 360;
349
+ if ((beta > -90 && beta < 90)) {
350
+ const dist = sin(abs(beta)) * geo.distanceBetween(way.path[minIdx], point);
351
+ const distPath = cos(abs(beta)) * geo.distanceBetween(way.path[minIdx], point);
352
+ if (dist < minDistance && distPath > 0 && distPath < geo.distanceBetween(way.path[minIdx], way.path[minIdx + 1]))
353
+ minDistance = dist;
354
+ }
355
+ }
356
+ return minDistance;
357
+ }
358
+ static addNode(node, nodesLookup, id, path) {
359
+ let point = nodesLookup[node];
360
+ if (point !== undefined)
361
+ path.push(point);
362
+ if (point.ways === undefined)
363
+ point.ways = [];
364
+ let found = point.ways.find((e) => { return e === id; });
365
+ if (found === undefined)
366
+ nodesLookup[node].ways.push(id);
367
+ }
368
+ static addWay(w, ways, waysLookup) {
369
+ if (waysLookup[w.id] !== undefined)
370
+ ways = ways.filter(e => e.id !== w.id);
371
+ ways.push(w);
372
+ waysLookup[w.id] = w;
373
+ }
374
+ static updateTypeStats(types, type) {
375
+ let t = (type === undefined ? '-' : type);
376
+ if (types[t] !== undefined)
377
+ types[t]++;
378
+ else
379
+ types[t] = 1;
380
+ }
381
+ static _parse(str, filter) {
382
+ if (str === undefined)
383
+ return;
384
+ let data;
385
+ if (typeof (str) === 'string') {
386
+ try {
387
+ data = JSON.parse(str);
388
+ }
389
+ catch (error) {
390
+ return;
391
+ }
392
+ }
393
+ else {
394
+ data = str;
395
+ }
396
+ if (data.elements === undefined)
397
+ return;
398
+ let nodesLookup = [];
399
+ let waysLookup = [];
400
+ let ways = [];
401
+ let types = [];
402
+ let idx = 0;
403
+ let id;
404
+ data.elements.forEach(element => {
405
+ if (element.type === 'node') {
406
+ id = (element.id !== undefined && typeof element.id === 'number') ? id = element.id.toString() : '_INT_' + idx;
407
+ nodesLookup[element.id] = { id, lat: element.lat, lng: element.lon, tags: element.tags };
408
+ idx++;
409
+ }
410
+ });
411
+ idx = 0;
412
+ data.elements.forEach(element => {
413
+ let path = [];
414
+ if (element.type === 'way') {
415
+ id = (element.id !== undefined && typeof element.id === 'number') ? id = element.id.toString() : '_INT_' + idx;
416
+ let type = undefined;
417
+ let name = undefined;
418
+ if (element.tags !== undefined) {
419
+ name = element.tags.name;
420
+ type = element.tags.highway;
421
+ }
422
+ if (type !== undefined && (filter === undefined || filter.find(e => e === type) === undefined)) {
423
+ element.nodes.forEach(node => {
424
+ MapAreaService.addNode(node, nodesLookup, id, path);
425
+ });
426
+ MapAreaService.updateTypeStats(types, type);
427
+ MapAreaService.addWay({ id, path, type, name, tags: element.tags, bounds: element.bounds }, ways, waysLookup);
428
+ idx++;
429
+ }
430
+ }
431
+ });
432
+ return { ways, nodesLookup, waysLookup, typeStats: types };
433
+ }
434
+ splitAtFirstBranch(way) {
435
+ if (way === undefined || way.path === undefined)
436
+ return;
437
+ let result = {
438
+ way: { id: way.id, path: [] },
439
+ branches: []
440
+ };
441
+ try {
442
+ let self = this;
443
+ let pointInfo = getFirstBranch(way);
444
+ if (pointInfo !== undefined) {
445
+ let paths = splitAtPointInfo(way, pointInfo);
446
+ result.way.path = paths[0];
447
+ let branch = { id: way.id, path: paths[1] };
448
+ if (branch.path.length > 1)
449
+ result.branches.push(branch);
450
+ pointInfo.point.ways.forEach((wid) => {
451
+ let w = clone(self.waysLookup[wid]);
452
+ if (w !== undefined && w.id !== way.id) {
453
+ if (w.path[0].id === pointInfo.point.id) {
454
+ branch = getUntilFirstBranch(w, { ignore: way.id });
455
+ if (branch.path.length > 1)
456
+ result.branches.push(branch);
457
+ }
458
+ else if (w.path[w.path.length - 1].id === pointInfo.point.id) {
459
+ w.path.reverse();
460
+ branch = getUntilFirstBranch(w, { ignore: way.id });
461
+ if (branch.path.length > 1)
462
+ result.branches.push(branch);
463
+ }
464
+ else {
465
+ let branches = splitAtPoint(w, pointInfo.point);
466
+ branch = getUntilFirstBranch(branches[0], { ignore: way.id });
467
+ if (branch.path.length > 1)
468
+ result.branches.push(branch);
469
+ branch = getUntilFirstBranch(branches[1], { ignore: way.id });
470
+ if (branch.path.length > 1)
471
+ result.branches.push(branch);
472
+ }
473
+ }
474
+ });
475
+ }
476
+ else {
477
+ result.way.path = [...way.path];
478
+ }
479
+ }
480
+ catch (error) {
481
+ this.logger.logEvent({ message: 'error', fn: 'splitAtFirstBranch()', way, error: error.message, stack: error.stack });
482
+ result = undefined;
483
+ }
484
+ return result;
485
+ }
486
+ getWay(props) {
487
+ if (!props)
488
+ return;
489
+ if (typeof props === 'string') {
490
+ return clone(this.waysLookup[props]);
491
+ }
492
+ if (props.id !== undefined)
493
+ return clone(this.waysLookup[props.id]);
494
+ }
495
+ getNode(props) {
496
+ if (!props)
497
+ return this.location;
498
+ if (typeof props === 'string') {
499
+ return clone(this.nodesLookup[props]);
500
+ }
501
+ if (typeof props === 'object' && props.id === undefined && props.lat !== undefined && props.lng !== undefined && props.ways !== undefined) {
502
+ return clone(props);
503
+ }
504
+ if (props.id !== undefined) {
505
+ return clone(this.nodesLookup[props.id]);
506
+ }
507
+ }
508
+ copyWay(wayFrom, id) {
509
+ let w;
510
+ w = (id === undefined) ? this.getWay(wayFrom) : this.getWay(id);
511
+ let path = w.path;
512
+ let roundabout = isRoundabout(w);
513
+ if (!roundabout && wayFrom !== undefined && wayFrom.id === w.id) {
514
+ let idxPrev = undefined;
515
+ let idx = undefined;
516
+ let point = wayFrom.path[wayFrom.path.length - 1];
517
+ let prev = wayFrom.path[wayFrom.path.length - 2];
518
+ path.forEach((p, i) => {
519
+ if (idx === undefined || idxPrev === undefined) {
520
+ if (pointEquals(p, point))
521
+ idx = i;
522
+ if (pointEquals(p, prev))
523
+ idxPrev = i;
524
+ }
525
+ });
526
+ if (idx !== undefined && idxPrev !== undefined) {
527
+ if (idxPrev < idx)
528
+ w.path = path.filter((n, i) => i >= idx);
529
+ if (idxPrev > idx) {
530
+ w.path = path.filter((n, i) => i <= idx);
531
+ w.path.reverse();
532
+ }
533
+ }
534
+ }
535
+ return { w, roundabout };
536
+ }
537
+ checkOptionsOnCurrentWay(location, way, options) {
538
+ try {
539
+ if (way.path.length > 1) {
540
+ let prev = way.path[way.path.length - 2];
541
+ let { w, roundabout } = this.copyWay(way);
542
+ if (roundabout) {
543
+ let branches = this.splitWayAtPoint(w, location, way);
544
+ branches.forEach(b => {
545
+ if (b.path.length > 1 && b.path[1].id !== prev.id) {
546
+ options.push(b);
547
+ }
548
+ });
549
+ }
550
+ else {
551
+ if (w.path.length > 1) {
552
+ let result = this.splitAtFirstBranch(w);
553
+ options.push(result.way);
554
+ }
555
+ }
556
+ }
557
+ }
558
+ catch (e) {
559
+ console.log(e);
560
+ }
561
+ }
562
+ splitWayAtPoint(w, location) {
563
+ return splitAtPoint(w, location);
564
+ }
565
+ checkOptionsOnDifferentWay(location, w, options) {
566
+ if (!w || !w.path || !location)
567
+ return;
568
+ let result;
569
+ let roundabout = isRoundabout(w);
570
+ let self = this;
571
+ if (!roundabout) {
572
+ if (w.path[0].id === location.id) {
573
+ result = this.splitAtFirstBranch(w);
574
+ let expand = false;
575
+ if (result.way.path.length === w.path.length) {
576
+ const pLast = w.path[w.path.length - 1];
577
+ if (pLast.ways.length === 2) {
578
+ const wIdNext = pLast.ways.find(wid => wid !== w.id);
579
+ const wNext = this.getWay({ id: wIdNext });
580
+ if (!isRoundabout(wNext)) {
581
+ const pNextStart = wNext.path[0];
582
+ const pNextEnd = wNext.path[wNext.path.length - 1];
583
+ if (pNextStart.id === pLast.id) {
584
+ expand = true;
585
+ const segment = this.splitAtFirstBranch(wNext);
586
+ const combined = {
587
+ id: segment.way.id,
588
+ path: w.path.concat(segment.way.path.slice(1))
589
+ };
590
+ options.push(combined);
591
+ }
592
+ else if (pNextEnd.id === pLast.id) {
593
+ const wReverse = this.copyWay(wNext);
594
+ if (wReverse && wReverse.path) {
595
+ wReverse.path = wReverse.path.reverse();
596
+ const segment = this.splitAtFirstBranch(wNext);
597
+ const combined = {
598
+ id: segment.way.id,
599
+ path: w.path.concat(segment.way.path.slice(1))
600
+ };
601
+ options.push(combined);
602
+ }
603
+ else {
604
+ console.log(' ~~~~ unexpected', wNext, wReverse);
605
+ }
606
+ }
607
+ else {
608
+ const idxSplit = wNext.path.findIndex((p) => p.id === pLast.id);
609
+ if (idxSplit !== 1) {
610
+ const nextPaths = splitAtIndex(wNext, idxSplit);
611
+ nextPaths.forEach((p) => {
612
+ if (p[0].ways.find(wid => wid === w.id)) {
613
+ expand = true;
614
+ const wFull = {
615
+ id: wIdNext,
616
+ path: p.slice(1)
617
+ };
618
+ const segment = this.splitAtFirstBranch(wFull);
619
+ const combined = {
620
+ id: segment.way.id,
621
+ path: w.path.concat(segment.way.path.slice(1))
622
+ };
623
+ options.push(combined);
624
+ }
625
+ else if (p[p.length - 1].ways.find(wid => wid === w.id)) {
626
+ expand = true;
627
+ const wFull = {
628
+ id: wIdNext,
629
+ path: p.reverse()
630
+ };
631
+ const segment = this.splitAtFirstBranch(wFull);
632
+ const combined = {
633
+ id: segment.way.id,
634
+ path: w.path.concat(segment.way.path.slice(1))
635
+ };
636
+ options.push(combined);
637
+ }
638
+ });
639
+ }
640
+ }
641
+ }
642
+ }
643
+ }
644
+ if (!expand)
645
+ options.push(result.way);
646
+ }
647
+ else if (w.path[w.path.length - 1].id === location.id) {
648
+ w.path.reverse();
649
+ result = this.splitAtFirstBranch(w);
650
+ if (isOneWay(w))
651
+ result.way.onewayReverse = true;
652
+ options.push(result.way);
653
+ }
654
+ else {
655
+ let ways = splitAtPoint(w, location);
656
+ if (!ways[0].onewayReverse) {
657
+ result = this.splitAtFirstBranch(ways[0]);
658
+ options.push(result.way);
659
+ }
660
+ if (!ways[1].onewayReverse) {
661
+ result = this.splitAtFirstBranch(ways[1]);
662
+ options.push(result.way);
663
+ }
664
+ }
665
+ }
666
+ else {
667
+ let r = splitAtPoint(w, location);
668
+ if (!r || r.length === 0)
669
+ return;
670
+ let path = [];
671
+ r[0].path.forEach((p, idx) => {
672
+ if (idx === 0) {
673
+ path.push(p);
674
+ return;
675
+ }
676
+ if (p.ways.length > 1) {
677
+ p.ways.forEach(owid => {
678
+ if (owid !== w.id) {
679
+ let ow = clone(this.waysLookup[owid]);
680
+ if (ow !== undefined) {
681
+ if (!isAllowed(ow, p))
682
+ return;
683
+ if (ow.path[ow.path.length - 1].id === p.id)
684
+ ow.path.reverse();
685
+ let optPath1 = self.splitAtFirstBranch(ow);
686
+ let optPath = [...path];
687
+ optPath.push(...optPath1.way.path);
688
+ let o = {
689
+ roundabout: w.id,
690
+ id: ow.id,
691
+ path: optPath
692
+ };
693
+ options.push(o);
694
+ }
695
+ }
696
+ });
697
+ }
698
+ path.push(p);
699
+ });
700
+ }
701
+ return this.removeDuplicates(options);
702
+ }
703
+ removeDuplicates(options) {
704
+ const endPoints = [];
705
+ const unique = [];
706
+ const uniqueUsed = [];
707
+ options.forEach((o) => { endPoints.push(o.path[o.path.length - 1].id); });
708
+ endPoints.forEach((id) => {
709
+ if (unique.indexOf(id) < 0) {
710
+ unique.push(id);
711
+ uniqueUsed.push(false);
712
+ }
713
+ });
714
+ return options.filter((o) => {
715
+ const id = o.path[o.path.length - 1].id;
716
+ const idx = unique.indexOf(id);
717
+ if (idx >= 0 && !uniqueUsed[idx]) {
718
+ uniqueUsed[idx] = true;
719
+ return true;
720
+ }
721
+ return false;
722
+ });
723
+ }
724
+ _getNextOptions(loc, way, way1, status) {
725
+ let self = this;
726
+ let location = self.getNode(loc);
727
+ let options = [];
728
+ if (location !== undefined) {
729
+ location.ways.forEach((wid, idx) => {
730
+ if (wid === way.id) {
731
+ self.checkOptionsOnCurrentWay(location, way, options);
732
+ }
733
+ else if (way1 !== undefined && wid === way1.id) {
734
+ }
735
+ else {
736
+ let w = self.getWay(wid);
737
+ options = self.checkOptionsOnDifferentWay(location, w, options);
738
+ }
739
+ });
740
+ }
741
+ return options;
742
+ }
743
+ getNextOptions(way, way1 = undefined, reload = false, props) {
744
+ return new Promise((resolve, reject) => {
745
+ if (way === undefined || way.id === undefined || way.path === undefined || way.path.length < 1)
746
+ return reject({ retry: false, message: 'invalid arguments' });
747
+ let self = this;
748
+ let location = way.path[way.path.length - 1];
749
+ if (location.id === undefined) {
750
+ if (way.path.length > 1)
751
+ location = way.path[way.path.length - 2];
752
+ else {
753
+ return resolve([]);
754
+ }
755
+ }
756
+ let currentWay = way;
757
+ if (way.path[0].id === undefined) {
758
+ currentWay = this.copyWay(way);
759
+ }
760
+ if (props && props.mapReload === false) {
761
+ resolve(self._getNextOptions(location, currentWay, way1));
762
+ }
763
+ else {
764
+ this.setLocation(location, reload, (status, ways, data) => {
765
+ if (status !== 'success') {
766
+ reject({ retry: true, code: 1, message: 'could not load data' });
767
+ }
768
+ resolve(self._getNextOptions(location, currentWay, way1));
769
+ });
770
+ }
771
+ });
772
+ }
773
+ static _isCrossing(way1, way2, exact = true) {
774
+ let found = [];
775
+ let pPrev = undefined;
776
+ let distance = 0;
777
+ way1.path.forEach((p, idx) => {
778
+ if (pPrev !== undefined) {
779
+ distance += geo.distanceBetween(pPrev, p);
780
+ }
781
+ if (p.ways.find(e => e === way2.id) !== undefined) {
782
+ let x = JSON.parse(JSON.stringify(p));
783
+ x.distance = Number(Number.parseFloat(distance).toFixed());
784
+ found.push(x);
785
+ }
786
+ pPrev = p;
787
+ });
788
+ return found;
789
+ }
790
+ static _crossing(A, B, C, D, exact = true) {
791
+ let AB = getVector(A, B);
792
+ let CD = getVector(C, D);
793
+ let AC = getVector(A, C);
794
+ A.distance = 0;
795
+ B.distance = geo.distanceBetween(A, B);
796
+ if (MapAreaService._isWithinRange(geo.distanceBetween(A, C)))
797
+ return A;
798
+ if (MapAreaService._isWithinRange(geo.distanceBetween(A, D)))
799
+ return A;
800
+ if (MapAreaService._isWithinRange(geo.distanceBetween(B, C)))
801
+ return B;
802
+ if (MapAreaService._isWithinRange(geo.distanceBetween(B, D)))
803
+ return B;
804
+ let V = crossing(AB, CD, AC);
805
+ if (V === undefined)
806
+ return undefined;
807
+ let p1Len = AB.len();
808
+ let distance = V.len();
809
+ if (AB.isSameDirection(V) && (distance < p1Len || MapAreaService._isWithinRange(p1Len - distance))) {
810
+ let X = geo.getPointBetween(A, B, distance);
811
+ let CX = getVector(C, X);
812
+ if (!exact || (CX.len() < CD.len() || MapAreaService._isWithinRange(CD.len() - CX.len))) {
813
+ X.distance = distance;
814
+ return X;
815
+ }
816
+ }
817
+ return undefined;
818
+ }
819
+ static _getBoundary(location, radius) {
820
+ return getBounds(location.lat, location.lng, radius);
821
+ }
822
+ isWithinBoundary(location) {
823
+ return isWithinBoundary(location, this.boundary);
824
+ }
825
+ static _generateQuery(location, radius) {
826
+ const boundary = MapAreaService._getBoundary(location, radius);
827
+ const template = GET_WAYS_IN_AREA;
828
+ return template.replace('__boundary__', MapAreaService._boundaryToString(boundary));
829
+ }
830
+ static _boundaryToString(boundary) {
831
+ if (boundary === undefined)
832
+ return 'undefined';
833
+ let ne = boundary.northeast;
834
+ let sw = boundary.southwest;
835
+ if (ne !== undefined && (ne.lat === undefined || ne.lng === undefined))
836
+ return 'error: northeast incorrect';
837
+ if (sw !== undefined && (sw.lat === undefined || sw.lng === undefined))
838
+ return 'error: southwest incorrect';
839
+ if (ne === undefined)
840
+ ne = sw;
841
+ if (sw === undefined)
842
+ sw = ne;
843
+ if (sw === undefined)
844
+ return 'undefined';
845
+ let str = sw.lat + ',' + sw.lng + ',' + ne.lat + ',' + ne.lng;
846
+ return str;
847
+ }
848
+ }
849
+ MapAreaService.consts = { DEFAULT_RADIUS, DEFAULT_MIN_WAYS, DEFAULT_MAX_WAYS, MAX_DISTANCE_FROM_PATH, GET_WAYS_IN_AREA };
850
+ MapAreaService._instances = {};
851
+ exports.default = MapAreaService;
852
+ function splitAtPointInfo(way, pointInfo) {
853
+ if (!way)
854
+ return;
855
+ let path = way.path;
856
+ let result = [[], []];
857
+ let foundBranch2 = false;
858
+ path.forEach((p, idx) => {
859
+ if (idx <= pointInfo.idx)
860
+ result[0].push(p);
861
+ if (idx === pointInfo.idx) {
862
+ result[1].push(p);
863
+ }
864
+ if (!foundBranch2 && idx > pointInfo.idx && p.ways.length === 1)
865
+ result[1].push(p);
866
+ if (!foundBranch2 && idx > pointInfo.idx && p.ways.length > 1) {
867
+ if (findAdditional(pointInfo.branches, p.ways, way.id, (id1, id2) => (id1 === id2)) !== undefined)
868
+ foundBranch2 = true;
869
+ result[1].push(p);
870
+ }
871
+ });
872
+ return result;
873
+ }
874
+ exports.splitAtPointInfo = splitAtPointInfo;
875
+ function getUntilFirstBranch(w, props) {
876
+ if (!w)
877
+ return;
878
+ let reverse = (props !== undefined) ? props.reverse : false;
879
+ let ignore = (props !== undefined) ? props.ignore : undefined;
880
+ let branch;
881
+ let path = w.path;
882
+ if (reverse) {
883
+ w.path = [...path];
884
+ w.path.reverse();
885
+ path = w.path;
886
+ }
887
+ let piBranch = getFirstBranch(w, ignore);
888
+ if (piBranch !== undefined) {
889
+ branch = { id: w.id, path: [] };
890
+ path.forEach((p, idx) => {
891
+ if (idx <= piBranch.idx)
892
+ branch.path.push(p);
893
+ });
894
+ }
895
+ else {
896
+ branch = { id: w.id, path: w.path };
897
+ }
898
+ return branch;
899
+ }
900
+ exports.getUntilFirstBranch = getUntilFirstBranch;
901
+ function isRoundabout(w, strictCheck = false) {
902
+ if (!w)
903
+ return;
904
+ let roundabout = (w.tags !== undefined && (w.tags.roundabout === true || w.tags.junction === 'roundabout'));
905
+ if (roundabout)
906
+ return true;
907
+ if (strictCheck)
908
+ return false;
909
+ if (w.path === undefined || w.path.length < 2)
910
+ return false;
911
+ roundabout = (w.path[0].id === w.path[w.path.length - 1].id);
912
+ return roundabout;
913
+ }
914
+ exports.isRoundabout = isRoundabout;
915
+ function splitAtIndex(way, idxSplit) {
916
+ let path = way.path;
917
+ let result = [[], []];
918
+ path.forEach((p, idx) => {
919
+ if (idx <= idxSplit)
920
+ result[0].push(p);
921
+ if (idx >= idxSplit)
922
+ result[1].push(p);
923
+ });
924
+ return result;
925
+ }
926
+ exports.splitAtIndex = splitAtIndex;
927
+ function splitAtPoint(way, point) {
928
+ let path = way.path;
929
+ let result = [{ id: way.id, path: [] }, { id: way.id, path: [] }];
930
+ let found = false;
931
+ let roundabout = isRoundabout(way);
932
+ if (!roundabout) {
933
+ path.forEach((p, idx) => {
934
+ if (!found)
935
+ result[0].path.push(p);
936
+ if (!found)
937
+ found = pointEquals(p, point);
938
+ if (found)
939
+ result[1].path.push(p);
940
+ });
941
+ result[0].path.reverse();
942
+ if (isOneWay(way))
943
+ result[0].onewayReverse = true;
944
+ }
945
+ else {
946
+ let idx = undefined;
947
+ path.forEach((p, i) => {
948
+ if (!found) {
949
+ found = pointEquals(p, point);
950
+ if (found)
951
+ idx = i;
952
+ }
953
+ });
954
+ if (idx !== undefined) {
955
+ let i;
956
+ for (i = 0; i < path.length; i++) {
957
+ let len = path.length;
958
+ let p = path[(idx + i) % len];
959
+ let q = path[(idx - i + len) % len];
960
+ if (result[0].path.length === 0 ||
961
+ (p.id !== result[0].path[result[0].path.length - 1].id && p.id !== result[0].path[0].id))
962
+ result[0].path.push(p);
963
+ if (result[1].path.length === 0 ||
964
+ (q.id !== result[1].path[result[1].path.length - 1].id && q.id !== result[1].path[0].id))
965
+ result[1].path.push(q);
966
+ }
967
+ if (isOneWay(way)) {
968
+ result[1].onewayReverse = true;
969
+ }
970
+ }
971
+ else {
972
+ return ([]);
973
+ }
974
+ }
975
+ return result;
976
+ }
977
+ exports.splitAtPoint = splitAtPoint;
978
+ function pointEquals(p1, p2) {
979
+ if (p1 === undefined || p2 === undefined)
980
+ return false;
981
+ return (p1.id === p2.id || (p1.lat !== undefined && p1.lng !== undefined && p1.lat === p2.lat && p1.lng === p2.lng));
982
+ }
983
+ exports.pointEquals = pointEquals;
984
+ function isWay(p) {
985
+ if (p === undefined || p === null || p.id === undefined || p.path === undefined || p.tags === undefined)
986
+ return false;
987
+ return true;
988
+ }
989
+ exports.isWay = isWay;
990
+ function isNode(p) {
991
+ if (p === undefined || p === null || p.id === undefined || p.lat === undefined || p.lng === undefined || p.ways === undefined)
992
+ return false;
993
+ return true;
994
+ }
995
+ exports.isNode = isNode;
996
+ function isAllowed(way, from, to) {
997
+ if (isNode(from) && isWay(way)) {
998
+ const fromIdx = way.path.findIndex(p => p.id === from.id);
999
+ if (fromIdx !== -1 && !isOneWay(way))
1000
+ return true;
1001
+ let p0 = way.path[0];
1002
+ let pN = way.path[way.path.length - 1];
1003
+ if (from.id === p0.id)
1004
+ return true;
1005
+ if (from.id !== p0.id && from.id === pN.id)
1006
+ return !isOneWay(way);
1007
+ if (isNode(to) && to.id === pN.id)
1008
+ return true;
1009
+ if (isNode(from) && isNode(to)) {
1010
+ const toId = way.path.findIndex(p => p.id === to.id);
1011
+ if (fromIdx === -1 || toId === -1)
1012
+ return;
1013
+ return !isOneWay(way) || fromIdx < toId;
1014
+ }
1015
+ }
1016
+ return undefined;
1017
+ }
1018
+ exports.isAllowed = isAllowed;
1019
+ function isOneWay(way) {
1020
+ if (!way || !way.tags || !way.tags.oneway)
1021
+ return false;
1022
+ let ow = way.tags.oneway;
1023
+ return (ow === true || ow === 'yes');
1024
+ }
1025
+ exports.isOneWay = isOneWay;
1026
+ function getFirstBranch(way, ignore) {
1027
+ if (way === undefined || way.path === undefined)
1028
+ return;
1029
+ let pFound = undefined;
1030
+ way.path.forEach((p, idx) => {
1031
+ if (idx === 0)
1032
+ return;
1033
+ let ways = [];
1034
+ p.ways.forEach((w) => {
1035
+ if (ignore === undefined || w !== ignore)
1036
+ ways.push(w);
1037
+ });
1038
+ if (!pFound && p.ways !== undefined && ways.length > 1) {
1039
+ let branches = [];
1040
+ ways.forEach(pw => {
1041
+ if (pw !== way.id)
1042
+ branches.push(pw);
1043
+ });
1044
+ pFound = { point: p, idx, branches };
1045
+ }
1046
+ });
1047
+ return pFound;
1048
+ }
1049
+ exports.getFirstBranch = getFirstBranch;
1050
+ function findAdditional(a1, a2, id3, fn) {
1051
+ let res = [];
1052
+ a1.forEach(e1 => {
1053
+ a2.forEach(e2 => {
1054
+ if (!fn(e1, e2) && !fn(e1, id3) && !fn(e2, id3))
1055
+ res.push(e2);
1056
+ });
1057
+ });
1058
+ if (res.length === 0)
1059
+ return undefined;
1060
+ return res;
1061
+ }
1062
+ exports.findAdditional = findAdditional;
1063
+ function _getPointOnLine(point, p1, p2) {
1064
+ let bL = initialBearingTo(p1, p2);
1065
+ let bP = bL + 90;
1066
+ let p3 = destinationPoint(point, 100, bP);
1067
+ return MapAreaService._crossing(p1, p2, point, p3, false);
1068
+ }
1069
+ exports._getPointOnLine = _getPointOnLine;
1070
+ function getPointCrossingPath(point, path, closest = false) {
1071
+ let pPrev = undefined;
1072
+ let X = undefined;
1073
+ let pClosest = undefined;
1074
+ if (path === undefined)
1075
+ return undefined;
1076
+ path.forEach((p, i) => {
1077
+ let pDist = abs(geo.distanceBetween(p, point));
1078
+ if (pClosest === undefined || pClosest.distance > pDist) {
1079
+ pClosest = { distance: pDist, point: p, idx: i };
1080
+ }
1081
+ if (pPrev !== undefined) {
1082
+ let pX = _getPointOnLine(point, pPrev, p);
1083
+ if (pX !== undefined) {
1084
+ let dist = abs(geo.distanceBetween(pX, point));
1085
+ if (X === undefined || (X.distance > dist)) {
1086
+ X = { point: pX, distance: dist, idx: i };
1087
+ }
1088
+ }
1089
+ }
1090
+ pPrev = p;
1091
+ });
1092
+ if (X !== undefined) {
1093
+ return X;
1094
+ }
1095
+ else {
1096
+ if (closest)
1097
+ return pClosest;
1098
+ }
1099
+ }
1100
+ exports.getPointCrossingPath = getPointCrossingPath;
1101
+ function getCrossingInfo(way1, way2) {
1102
+ if (way1 === undefined || way1.path === undefined || way2 === undefined || way2.path === undefined)
1103
+ return;
1104
+ let crossing = undefined;
1105
+ way1.path.forEach((p1, i) => {
1106
+ way2.path.forEach((p2, j) => {
1107
+ if (crossing === undefined && p1.id === p2.id) {
1108
+ crossing = { point: p1, idx1: i, idx2: j };
1109
+ }
1110
+ });
1111
+ });
1112
+ return crossing;
1113
+ }
1114
+ exports.getCrossingInfo = getCrossingInfo;
1115
+ function getVector(p1, p2) {
1116
+ if (p1 === undefined)
1117
+ throw new Error("missing mandatory argument: p1");
1118
+ if (p2 === undefined)
1119
+ throw new Error("missing mandatory argument: p2");
1120
+ let distance = geo.distanceBetween(p1, p2);
1121
+ if (distance === 0) {
1122
+ return new Vector([0, 0]);
1123
+ }
1124
+ let bearing = initialBearingTo(p1, p2);
1125
+ return new Vector({ path: { bearing, distance } });
1126
+ }
1127
+ exports.getVector = getVector;
1128
+ const rEarth = 6378.100;
1129
+ function getBounds(lat, lng, offset) {
1130
+ const pi = Math.PI;
1131
+ const m = (1 / ((2 * pi / 360) * rEarth)) / 1000;
1132
+ let boundary = {
1133
+ northeast: {},
1134
+ southwest: {}
1135
+ };
1136
+ boundary.northeast.lat = lat + offset * m;
1137
+ boundary.northeast.lng = lng + (offset * m) / Math.cos(lat * pi / 180);
1138
+ boundary.southwest.lat = lat - offset * m;
1139
+ boundary.southwest.lng = lng - (offset * m) / Math.cos(lat * pi / 180);
1140
+ return boundary;
1141
+ }
1142
+ exports.getBounds = getBounds;
1143
+ function isWithinBoundary(location, boundary) {
1144
+ if (location === undefined || location.lat === undefined || location.lng === undefined)
1145
+ return false;
1146
+ const lat = location.lat;
1147
+ const lng = location.lng;
1148
+ const sw = boundary.southwest;
1149
+ const ne = boundary.northeast;
1150
+ return (lat >= sw.lat && lat <= ne.lat && lng >= sw.lng && lng <= ne.lng);
1151
+ }
1152
+ exports.isWithinBoundary = isWithinBoundary;
1153
+ function initialBearingTo(p1, p2) {
1154
+ if (p1 === undefined || p2 === undefined || (p1.lat === p2.lat && p1.lng === p2.lng))
1155
+ return;
1156
+ const φ1 = rad(p1.lat);
1157
+ const φ2 = rad(p2.lat);
1158
+ const Δλ = rad(p2.lng - p1.lng);
1159
+ const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ);
1160
+ const y = Math.sin(Δλ) * Math.cos(φ2);
1161
+ const θ = Math.atan2(y, x);
1162
+ const bearing = degrees(θ);
1163
+ return bearing;
1164
+ }
1165
+ exports.initialBearingTo = initialBearingTo;
1166
+ function alongTrackDistanceTo(point, pathStart, pathEnd, radius = rEarth * 1000) {
1167
+ if (point === undefined || pathStart === undefined || pathEnd === undefined)
1168
+ return;
1169
+ const R = radius;
1170
+ const δ13 = geo.calculateDistance(pathStart.lat, pathStart.lng, point.lat, point.lng, R) / R;
1171
+ const θ13 = rad(initialBearingTo(pathStart, point));
1172
+ const θ12 = rad(initialBearingTo(pathStart, pathEnd));
1173
+ const δxt = Math.asin(Math.sin(δ13) * Math.sin(θ13 - θ12));
1174
+ const δat = Math.acos(Math.cos(δ13) / Math.abs(Math.cos(δxt)));
1175
+ let d = δat * Math.sign(Math.cos(θ12 - θ13)) * R;
1176
+ return d;
1177
+ }
1178
+ exports.alongTrackDistanceTo = alongTrackDistanceTo;
1179
+ function destinationPoint(start, distance, bearing, radius = rEarth * 1000) {
1180
+ const δ = distance / radius;
1181
+ const θ = rad(bearing);
1182
+ const φ1 = rad(start.lat), λ1 = rad(start.lng);
1183
+ const sinφ2 = Math.sin(φ1) * Math.cos(δ) + Math.cos(φ1) * Math.sin(δ) * Math.cos(θ);
1184
+ const φ2 = Math.asin(sinφ2);
1185
+ const y = Math.sin(θ) * Math.sin(δ) * Math.cos(φ1);
1186
+ const x = Math.cos(δ) - Math.sin(φ1) * sinφ2;
1187
+ const λ2 = λ1 + Math.atan2(y, x);
1188
+ const lat = degrees(φ2);
1189
+ const lng = degrees(λ2);
1190
+ return { lat, lng };
1191
+ }
1192
+ exports.destinationPoint = destinationPoint;
1193
+ const useMapArea = (id) => MapAreaService.getInstance(id);
1194
+ exports.useMapArea = useMapArea;