@maptrip/map-tiles 1.4.0 → 1.6.0
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/README.md +58 -4
- package/dist/api-extensions/leaflet/MapTripLeafletGridLayer.d.ts +9 -12
- package/dist/api-extensions/maplibre/MapLibreStyleFactory.d.ts +21 -0
- package/dist/core/VectorTileRenderer.d.ts +8 -0
- package/dist/core/constants.d.ts +3 -2
- package/dist/core/rendering_config_default.d.ts +120 -50
- package/dist/core/rendering_config_grayscale.d.ts +120 -50
- package/dist/core/rendering_config_traffic.d.ts +141 -6
- package/dist/map-tiles.js +1450 -576
- package/dist/map-tiles.js.map +1 -1
- package/dist/util/MapReleaseFetcher.d.ts +3 -2
- package/dist/util/VectorTileUtils.d.ts +4 -0
- package/lib/api-extensions/leaflet/MapTripLeafletGridLayer.ts +48 -95
- package/lib/api-extensions/maplibre/MapLibreStyleFactory.ts +114 -0
- package/lib/core/VectorTileFactory.ts +3 -2
- package/lib/core/VectorTileRenderer.ts +110 -37
- package/lib/core/constants.ts +8 -5
- package/lib/core/rendering_config_default.ts +384 -90
- package/lib/core/rendering_config_grayscale.ts +399 -105
- package/lib/core/rendering_config_traffic.ts +344 -104
- package/lib/util/MapReleaseFetcher.ts +38 -24
- package/lib/util/VectorTileUtils.ts +33 -0
- package/package.json +8 -7
|
@@ -5,7 +5,8 @@ export declare class MapReleaseFetcher {
|
|
|
5
5
|
private _osm;
|
|
6
6
|
private _here;
|
|
7
7
|
constructor(token: string);
|
|
8
|
-
getRelease(provider
|
|
9
|
-
|
|
8
|
+
getRelease(provider?: string): Promise<string | null>;
|
|
9
|
+
private getUrl;
|
|
10
|
+
fetchAndExtractRelease(provider: string): Promise<string | null>;
|
|
10
11
|
private extractTargetString;
|
|
11
12
|
}
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import L from "leaflet";
|
|
2
2
|
import { VectorTileFactory } from "../../core/VectorTileFactory.js";
|
|
3
|
-
import {
|
|
4
|
-
import { MapReleaseFetcher } from "../../util/MapReleaseFetcher.js";
|
|
3
|
+
import { VectorTileUtils } from "../../util/VectorTileUtils.js"
|
|
5
4
|
import type { PointXY } from "../../types/interfaces.js";
|
|
5
|
+
import { MIN_ZOOM, MAX_ZOOM } from "../../core/constants.js";
|
|
6
6
|
import { RENDERING_CONFIG } from "../../types/interfaces.js";
|
|
7
7
|
|
|
8
8
|
class MapTripLeafletGridLayer extends L.GridLayer {
|
|
9
|
-
|
|
10
|
-
MAX_ZOOM = MAX_ZOOM;
|
|
9
|
+
static attributions: string[] = [];
|
|
11
10
|
token: string;
|
|
12
11
|
factory: VectorTileFactory;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
provider?: string;
|
|
13
|
+
releaseDate?: boolean;
|
|
14
|
+
currentAttribution?: string;
|
|
15
|
+
private attachedMap?: L.Map;
|
|
16
|
+
private customAttribution?: string;
|
|
18
17
|
|
|
19
18
|
constructor(options: {
|
|
20
19
|
provider?: string;
|
|
@@ -30,110 +29,64 @@ class MapTripLeafletGridLayer extends L.GridLayer {
|
|
|
30
29
|
maxZoom?: number;
|
|
31
30
|
releaseDate?: boolean;
|
|
32
31
|
}) {
|
|
33
|
-
super(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
? options.
|
|
37
|
-
:
|
|
38
|
-
|
|
39
|
-
this.options.attribution = options.attribution
|
|
40
|
-
? options.attribution
|
|
41
|
-
: ATTRIBUTION;
|
|
42
|
-
this.releaseFetcher = new MapReleaseFetcher(this.token);
|
|
43
|
-
this.release = "";
|
|
44
|
-
this.attribution = "";
|
|
45
|
-
|
|
46
|
-
this.releaseDate = options.releaseDate ? options.releaseDate : false;
|
|
47
|
-
if (this.releaseDate) {
|
|
48
|
-
this.initializeAttribution();
|
|
49
|
-
this.on("add", () => {
|
|
50
|
-
if (this._map) {
|
|
51
|
-
this._map.on("baselayerchange", (event) =>
|
|
52
|
-
this.onBaseLayerChange(event)
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
}
|
|
32
|
+
super({
|
|
33
|
+
...options,
|
|
34
|
+
attribution: '',
|
|
35
|
+
minZoom: Math.max(options.minZoom ? options.minZoom : MIN_ZOOM, MIN_ZOOM),
|
|
36
|
+
maxZoom: Math.min(options.maxZoom ? options.maxZoom : MAX_ZOOM, MAX_ZOOM)
|
|
37
|
+
});
|
|
57
38
|
|
|
58
|
-
|
|
59
|
-
this.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
39
|
+
this.token = options.token!;
|
|
40
|
+
this.provider = options.provider;
|
|
41
|
+
this.customAttribution = options.attribution;
|
|
42
|
+
this.releaseDate = options.releaseDate;
|
|
43
|
+
this.currentAttribution = undefined;
|
|
44
|
+
|
|
45
|
+
void VectorTileUtils.getAttribution(this.token, this.provider, this.customAttribution, this.releaseDate)
|
|
46
|
+
.then(attribution => {
|
|
47
|
+
this.updateAttribution(attribution);
|
|
48
|
+
}).catch(error => {
|
|
49
|
+
console.error('Unable to add MapTrip attribution', error);
|
|
50
|
+
});
|
|
66
51
|
|
|
67
52
|
this.factory = VectorTileFactory.createFactory(options);
|
|
68
53
|
}
|
|
69
54
|
|
|
70
|
-
|
|
71
|
-
|
|
55
|
+
override onAdd(map: L.Map): this {
|
|
56
|
+
this.attachedMap = map;
|
|
57
|
+
return super.onAdd(map);
|
|
72
58
|
}
|
|
73
59
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
60
|
+
override onRemove(map: L.Map): this {
|
|
61
|
+
const result = super.onRemove(map);
|
|
62
|
+
this.attachedMap = undefined;
|
|
63
|
+
return result;
|
|
77
64
|
}
|
|
78
65
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const release = await this.releaseFetcher.getRelease(this.provider);
|
|
82
|
-
if (release) {
|
|
83
|
-
this.release = release;
|
|
84
|
-
}
|
|
66
|
+
createTile(coords: PointXY) {
|
|
67
|
+
return this.factory.getJsonTile(coords.z!, coords.x, coords.y, this.token);
|
|
85
68
|
}
|
|
86
69
|
|
|
87
|
-
private
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
// check if base layer changes to a new provider
|
|
91
|
-
const newRelease = await this.releaseFetcher.getRelease(this.provider);
|
|
92
|
-
if (newRelease && newRelease !== this.release) {
|
|
93
|
-
this.release = newRelease;
|
|
94
|
-
}
|
|
70
|
+
private updateAttribution(attribution: string): void {
|
|
71
|
+
const previous = this.options.attribution ?? '';
|
|
95
72
|
|
|
96
|
-
|
|
97
|
-
this._map.attributionControl.removeAttribution(this.options.attribution!);
|
|
98
|
-
this.attribution = `${this.options.attribution} ${this.release}`;
|
|
99
|
-
this._map.attributionControl.addAttribution(this.attribution!);
|
|
100
|
-
|
|
101
|
-
// remove the attribution with release date from map for instances where different layers have different preferences for setting release date
|
|
102
|
-
const map = this._map;
|
|
103
|
-
this._map.addEventListener("baselayerchange", () => {
|
|
104
|
-
map.attributionControl.removeAttribution(this.attribution);
|
|
105
|
-
});
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.error("Error fetching release version: ", error);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
private async onBaseLayerChange(event: L.LayersControlEvent) {
|
|
112
|
-
if (!this._map) {
|
|
73
|
+
if (previous === attribution) {
|
|
113
74
|
return;
|
|
114
75
|
}
|
|
115
76
|
|
|
116
|
-
this.
|
|
77
|
+
const map = this.attachedMap;
|
|
78
|
+
const control = map?.attributionControl;
|
|
117
79
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (eventProvider.toLowerCase() === "openstreetmap") {
|
|
123
|
-
newProvider = "openstreetmap";
|
|
124
|
-
} else if (eventProvider.toLowerCase() === "tomtom") {
|
|
125
|
-
newProvider = "tomtom";
|
|
126
|
-
} else if (eventProvider.toLowerCase() === "orbis") {
|
|
127
|
-
newProvider = "orbis";
|
|
128
|
-
} else if (eventProvider.toLowerCase() === "here") {
|
|
129
|
-
newProvider = "here";
|
|
130
|
-
} else {
|
|
131
|
-
newProvider = "openstreetmap";
|
|
132
|
-
}
|
|
80
|
+
// remove existing attribution
|
|
81
|
+
if (control && map.hasLayer(this)) {
|
|
82
|
+
control.removeAttribution(previous);
|
|
83
|
+
}
|
|
133
84
|
|
|
134
|
-
|
|
85
|
+
// set attribution and update control
|
|
86
|
+
this.options.attribution = attribution;
|
|
135
87
|
|
|
136
|
-
|
|
88
|
+
if (control && map.hasLayer(this)) {
|
|
89
|
+
control.addAttribution(attribution);
|
|
137
90
|
}
|
|
138
91
|
}
|
|
139
92
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { addProtocol, type StyleSpecification } from 'maplibre-gl';
|
|
2
|
+
|
|
3
|
+
import { VectorTileFactory } from "../../core/VectorTileFactory.js";
|
|
4
|
+
import { VectorTileUtils } from "../../util/VectorTileUtils.js"
|
|
5
|
+
import { MIN_ZOOM, MAX_ZOOM } from "../../core/constants.js";
|
|
6
|
+
import { RENDERING_CONFIG } from "../../types/interfaces.js";
|
|
7
|
+
|
|
8
|
+
class MapLibreStyleFactory {
|
|
9
|
+
|
|
10
|
+
private protocolId = 0;
|
|
11
|
+
|
|
12
|
+
public createMapTripStyle(options: {
|
|
13
|
+
provider?: string;
|
|
14
|
+
baseMap?: boolean;
|
|
15
|
+
traffic?: boolean;
|
|
16
|
+
timeout?: number;
|
|
17
|
+
parallelDownloads?: number;
|
|
18
|
+
detourProfile?: string;
|
|
19
|
+
renderingConfig?: RENDERING_CONFIG;
|
|
20
|
+
token?: string;
|
|
21
|
+
attribution?: string;
|
|
22
|
+
minZoom?: number;
|
|
23
|
+
maxZoom?: number;
|
|
24
|
+
releaseDate?: boolean;
|
|
25
|
+
}): StyleSpecification {
|
|
26
|
+
|
|
27
|
+
const token = options.token!;
|
|
28
|
+
const factory = VectorTileFactory.createFactory(options);
|
|
29
|
+
const protocol = `maptrip${++this.protocolId}`;
|
|
30
|
+
let attributionPromise: Promise<string> | undefined;
|
|
31
|
+
|
|
32
|
+
addProtocol(protocol, async (params) => {
|
|
33
|
+
|
|
34
|
+
if (params.url === `${protocol}://metadata`) {
|
|
35
|
+
|
|
36
|
+
const attribution = await (
|
|
37
|
+
attributionPromise ??= VectorTileUtils.getAttribution(token, options.provider, options.attribution, options.releaseDate)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
data: {
|
|
42
|
+
tilejson: '2.2.0',
|
|
43
|
+
tileSize: 256,
|
|
44
|
+
tiles: [`${protocol}://{z}/{x}/{y}`],
|
|
45
|
+
minzoom: Math.max(options.minZoom ? options.minZoom : MIN_ZOOM, MIN_ZOOM),
|
|
46
|
+
maxzoom: Math.min(options.maxZoom ? options.maxZoom : MAX_ZOOM, MAX_ZOOM),
|
|
47
|
+
attribution: attribution
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const match = params.url.match(
|
|
53
|
+
/^maptrip\d+:\/\/(\d+)\/(\d+)\/(\d+)$/
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (!match) {
|
|
57
|
+
throw new Error(`Invalid tile URL: ${params.url}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const z = Number(match[1]);
|
|
61
|
+
const x = Number(match[2]);
|
|
62
|
+
const y = Number(match[3]);
|
|
63
|
+
|
|
64
|
+
const canvas = await this.getTileCanvasAsync(factory, z, x, y, token);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
data: await createImageBitmap(canvas)
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
version: 8,
|
|
73
|
+
name: "MapTrip Map",
|
|
74
|
+
sources: {
|
|
75
|
+
maptrip: {
|
|
76
|
+
type: 'raster',
|
|
77
|
+
url: `${protocol}://metadata`,
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
layers: [
|
|
81
|
+
{
|
|
82
|
+
id: 'maptrip-layer',
|
|
83
|
+
type: 'raster',
|
|
84
|
+
source: 'maptrip'
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
private getTileCanvasAsync(
|
|
91
|
+
factory: VectorTileFactory,
|
|
92
|
+
z: number,
|
|
93
|
+
x: number,
|
|
94
|
+
y: number,
|
|
95
|
+
token: string
|
|
96
|
+
): Promise<HTMLCanvasElement> {
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const canvas = factory.getJsonTile(z, x, y, token, () => {
|
|
101
|
+
resolve(canvas);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (!canvas) {
|
|
105
|
+
reject(new Error('getJsonTile returned no canvas'));
|
|
106
|
+
}
|
|
107
|
+
} catch (err) {
|
|
108
|
+
reject(err);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export { MapLibreStyleFactory };
|
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
PROVIDER_PROJECTS,
|
|
8
8
|
PROVIDER_TRAFFIC,
|
|
9
9
|
VECTOR_TILE_SIZE,
|
|
10
|
-
VECTOR_TILE_RENDER_PADDING
|
|
10
|
+
VECTOR_TILE_RENDER_PADDING,
|
|
11
|
+
DEFAULT_PROVIDER
|
|
11
12
|
} from "./constants.js";
|
|
12
13
|
|
|
13
14
|
import WebFont from "webfontloader";
|
|
@@ -367,7 +368,7 @@ class VectorTileFactory {
|
|
|
367
368
|
}
|
|
368
369
|
|
|
369
370
|
static DEFAULT_PROPERTIES = {
|
|
370
|
-
provider:
|
|
371
|
+
provider: DEFAULT_PROVIDER,
|
|
371
372
|
baseMap: true,
|
|
372
373
|
traffic: false,
|
|
373
374
|
};
|
|
@@ -43,6 +43,10 @@ class VectorTileRenderer {
|
|
|
43
43
|
boundaries: Boundaries,
|
|
44
44
|
context: CanvasRenderingContext2D
|
|
45
45
|
): void {
|
|
46
|
+
|
|
47
|
+
context.lineJoin = 'round';
|
|
48
|
+
context.lineCap = 'round';
|
|
49
|
+
|
|
46
50
|
context.fillStyle = this.getBackgroundColor(z);
|
|
47
51
|
context.fillRect(0, 0, VECTOR_TILE_SIZE, VECTOR_TILE_SIZE);
|
|
48
52
|
|
|
@@ -79,6 +83,7 @@ class VectorTileRenderer {
|
|
|
79
83
|
);
|
|
80
84
|
|
|
81
85
|
this.drawTilePhase(polygons, z, boundaries, context, "areas", collisions);
|
|
86
|
+
// add phase areasOutline here if polygon outlines should be rendered
|
|
82
87
|
this.drawPolylineLayers(lines, z, boundaries, context, "", collisions);
|
|
83
88
|
this.drawTilePhase(points, z, boundaries, context, "texts", collisions);
|
|
84
89
|
this.drawTilePhase(lines, z, boundaries, context, "lineTexts", collisions);
|
|
@@ -119,6 +124,14 @@ class VectorTileRenderer {
|
|
|
119
124
|
"linesOutline",
|
|
120
125
|
collisions
|
|
121
126
|
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (let i = 0; i < rank.length; i++) {
|
|
130
|
+
// select all lines with this sort rank
|
|
131
|
+
let currentRank = lines.filter(
|
|
132
|
+
(it) => parseInt(it.properties!.sort_rank) === rank[i]
|
|
133
|
+
);
|
|
134
|
+
|
|
122
135
|
this.drawTilePhase(
|
|
123
136
|
currentRank,
|
|
124
137
|
z,
|
|
@@ -306,12 +319,18 @@ class VectorTileRenderer {
|
|
|
306
319
|
collisions
|
|
307
320
|
)
|
|
308
321
|
) {
|
|
309
|
-
const match = text.match(/([
|
|
322
|
+
const match = text.match(/([a-zA-Z])\s?([0-9]+[a-z]*).*/);
|
|
310
323
|
if (match) {
|
|
311
324
|
const letter = match[1];
|
|
312
325
|
const number = match[2];
|
|
313
|
-
|
|
314
|
-
|
|
326
|
+
|
|
327
|
+
// Stadsroutes are major roads in some cities in the Netherlands and start with s.
|
|
328
|
+
// Adding them here will render them like Bundesstraßen in Germany.
|
|
329
|
+
// If country-dependent rendering is added, it should be a white box with black border and text.
|
|
330
|
+
|
|
331
|
+
if (letter === "A" || letter === "B" || letter === "s") {
|
|
332
|
+
text = letter === "A" || letter === "B" ? number : letter + number;
|
|
333
|
+
|
|
315
334
|
this.drawImage(
|
|
316
335
|
coordinates,
|
|
317
336
|
boundaries,
|
|
@@ -430,7 +449,6 @@ class VectorTileRenderer {
|
|
|
430
449
|
|
|
431
450
|
context.globalAlpha = merged.opacity;
|
|
432
451
|
|
|
433
|
-
/// XXX edit here
|
|
434
452
|
if ('areas' !== phase || j === 0) {
|
|
435
453
|
// when rendering areas, only the first part of a polygon should begin a path
|
|
436
454
|
// (otherwise especially islands are clipped on some zoom levels)
|
|
@@ -438,11 +456,11 @@ class VectorTileRenderer {
|
|
|
438
456
|
}
|
|
439
457
|
|
|
440
458
|
const width = boundaries.max.lon - boundaries.min.lon;
|
|
441
|
-
|
|
459
|
+
const height = boundaries.max.lat - boundaries.min.lat;
|
|
442
460
|
if (width === 0 || height === 0) {
|
|
443
461
|
return;
|
|
444
462
|
}
|
|
445
|
-
|
|
463
|
+
|
|
446
464
|
var points: { x: number; y: number }[] = [];
|
|
447
465
|
|
|
448
466
|
for (let k = 0; k < part.length; k++) {
|
|
@@ -636,6 +654,37 @@ class VectorTileRenderer {
|
|
|
636
654
|
return points;
|
|
637
655
|
}
|
|
638
656
|
|
|
657
|
+
/**
|
|
658
|
+
* @private
|
|
659
|
+
*/
|
|
660
|
+
translatePolyline(points: { x: number; y: number }[], delta: number): void {
|
|
661
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
662
|
+
const direction = {
|
|
663
|
+
x: points[i + 1].x - points[i].x,
|
|
664
|
+
y: points[i + 1].y - points[i].y
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
const length = Math.sqrt(Math.pow(direction.x, 2) + Math.pow(direction.y, 2));
|
|
668
|
+
|
|
669
|
+
const normal = {
|
|
670
|
+
x: direction.y / length,
|
|
671
|
+
y: -direction.x / length
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
points[i] = {
|
|
675
|
+
x: points[i].x - delta * normal.x,
|
|
676
|
+
y: points[i].y - delta * normal.y
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
if (i === points.length - 2) {
|
|
680
|
+
points[i + 1] = {
|
|
681
|
+
x: points[i + 1].x - delta * normal.x,
|
|
682
|
+
y: points[i + 1].y - delta * normal.y
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
639
688
|
/**
|
|
640
689
|
* @private
|
|
641
690
|
*/
|
|
@@ -650,9 +699,15 @@ class VectorTileRenderer {
|
|
|
650
699
|
var merged = { ...VectorTileRenderer.DEFAULT_STYLE.LINE, ...style };
|
|
651
700
|
context.globalAlpha = merged.opacity;
|
|
652
701
|
|
|
702
|
+
let points = this.projectCoordinates(coordinates, boundaries);
|
|
703
|
+
|
|
704
|
+
if (merged.delta > 0) {
|
|
705
|
+
// translate traffic flow polylines to the right
|
|
706
|
+
this.translatePolyline(points, merged.delta);
|
|
707
|
+
}
|
|
708
|
+
|
|
653
709
|
if ("linesOutline" === phase) {
|
|
654
710
|
if (merged.outlineColor && merged.outlineWidth) {
|
|
655
|
-
let points = this.projectCoordinates(coordinates, boundaries);
|
|
656
711
|
this.drawPath(
|
|
657
712
|
points,
|
|
658
713
|
merged.outlineColor,
|
|
@@ -664,7 +719,6 @@ class VectorTileRenderer {
|
|
|
664
719
|
return;
|
|
665
720
|
}
|
|
666
721
|
|
|
667
|
-
let points = this.projectCoordinates(coordinates, boundaries);
|
|
668
722
|
this.drawPath(points, merged.color, merged.width, context);
|
|
669
723
|
|
|
670
724
|
if (merged.dashColor && merged.dashLength) {
|
|
@@ -675,48 +729,69 @@ class VectorTileRenderer {
|
|
|
675
729
|
|
|
676
730
|
// draw images on the line (e.g. arrows for one-way roads)
|
|
677
731
|
if (merged.url && this._images[merged.url]) {
|
|
678
|
-
|
|
679
|
-
|
|
732
|
+
const desiredDistance: number = 40;
|
|
733
|
+
const margin: number = Math.round(desiredDistance / 2);
|
|
734
|
+
|
|
735
|
+
let totalLength = 0;
|
|
736
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
737
|
+
totalLength += Math.hypot(
|
|
738
|
+
points[i + 1].x - points[i].x,
|
|
739
|
+
points[i + 1].y - points[i].y
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
let distance: number = margin;
|
|
744
|
+
let traveled: number = 0;
|
|
680
745
|
|
|
681
746
|
for (var i = 0; i < points.length - 1; i++) {
|
|
682
747
|
// calculate angle of this section of the line
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
748
|
+
const dx: number = points[i + 1].x - points[i].x;
|
|
749
|
+
const dy: number = points[i + 1].y - points[i].y;
|
|
750
|
+
let angle: number = Math.atan2(dy, dx);
|
|
686
751
|
|
|
687
752
|
if (merged.direction != "reverse") {
|
|
688
753
|
angle = angle + Math.PI;
|
|
689
754
|
}
|
|
690
755
|
|
|
691
756
|
// now calculate equidistant points on the polyline
|
|
692
|
-
|
|
757
|
+
const distanceToNext: number = Math.sqrt(dx * dx + dy * dy);
|
|
693
758
|
while (distance < distanceToNext) {
|
|
694
759
|
// there are more points on this section of the line
|
|
695
|
-
var f = distance / distanceToNext;
|
|
696
760
|
|
|
697
|
-
|
|
698
|
-
var y = points[i].y + f * dy;
|
|
761
|
+
if (traveled + distance <= totalLength - margin) {
|
|
699
762
|
|
|
700
|
-
|
|
763
|
+
const f: number = distance / distanceToNext;
|
|
701
764
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
context.translate(-x, -y);
|
|
765
|
+
const x: number = points[i].x + f * dx;
|
|
766
|
+
const y: number = points[i].y + f * dy;
|
|
705
767
|
|
|
706
|
-
|
|
707
|
-
this._images[merged.url],
|
|
708
|
-
x + merged.dx,
|
|
709
|
-
y + merged.dy,
|
|
710
|
-
Number(merged.width),
|
|
711
|
-
Number(merged.height)
|
|
712
|
-
);
|
|
768
|
+
const size: number = Math.max(12, 1.5 * merged.width);
|
|
713
769
|
|
|
714
|
-
|
|
770
|
+
if (x > size && y > size && x < VECTOR_TILE_SIZE - size && y < VECTOR_TILE_SIZE - size) {
|
|
771
|
+
context.save();
|
|
715
772
|
|
|
716
|
-
|
|
773
|
+
context.translate(x, y);
|
|
774
|
+
context.rotate(angle);
|
|
775
|
+
context.translate(-x, -y);
|
|
776
|
+
|
|
777
|
+
context.drawImage(
|
|
778
|
+
this._images[merged.url],
|
|
779
|
+
x - size / 2,
|
|
780
|
+
y - size / 2,
|
|
781
|
+
size,
|
|
782
|
+
size,
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
context.restore();
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
}
|
|
789
|
+
distance += desiredDistance;
|
|
717
790
|
}
|
|
718
791
|
|
|
719
|
-
|
|
792
|
+
traveled += distanceToNext;
|
|
793
|
+
distance -= distanceToNext;
|
|
794
|
+
// distance = (desiredDistance - (distanceToNext - distance)) % desiredDistance;
|
|
720
795
|
}
|
|
721
796
|
}
|
|
722
797
|
}
|
|
@@ -943,7 +1018,7 @@ class VectorTileRenderer {
|
|
|
943
1018
|
outline: number,
|
|
944
1019
|
context: CanvasRenderingContext2D
|
|
945
1020
|
): void {
|
|
946
|
-
var width = context.measureText(letter).width + outline;
|
|
1021
|
+
var width = context.measureText(letter).width + outline;
|
|
947
1022
|
|
|
948
1023
|
var x = position.x + 0.5 * Math.cos(position.angle) * width;
|
|
949
1024
|
var y = position.y + 0.5 * Math.sin(position.angle) * width;
|
|
@@ -974,10 +1049,7 @@ class VectorTileRenderer {
|
|
|
974
1049
|
|
|
975
1050
|
context.strokeStyle = color;
|
|
976
1051
|
context.lineWidth = width;
|
|
977
|
-
|
|
978
|
-
context.lineCap = "square";
|
|
979
|
-
context.lineJoin = "miter";
|
|
980
|
-
|
|
1052
|
+
|
|
981
1053
|
context.moveTo(points[0].x, points[0].y);
|
|
982
1054
|
for (var j = 1; j < points.length; j++) {
|
|
983
1055
|
context.lineTo(points[j].x, points[j].y);
|
|
@@ -1131,7 +1203,7 @@ class VectorTileRenderer {
|
|
|
1131
1203
|
if (text) {
|
|
1132
1204
|
let merged = { ...VectorTileRenderer.DEFAULT_STYLE.TEXT, ...text };
|
|
1133
1205
|
let font = `${merged.fontStyle} ${merged.fontWeight} ${merged.size}px ${merged.font}`.trim();
|
|
1134
|
-
|
|
1206
|
+
|
|
1135
1207
|
result.push(font);
|
|
1136
1208
|
}
|
|
1137
1209
|
}
|
|
@@ -1161,6 +1233,7 @@ class VectorTileRenderer {
|
|
|
1161
1233
|
dashColor: undefined,
|
|
1162
1234
|
dashLength: 0,
|
|
1163
1235
|
opacity: 1.0,
|
|
1236
|
+
delta: 0,
|
|
1164
1237
|
},
|
|
1165
1238
|
IMAGE: {
|
|
1166
1239
|
url: undefined,
|
package/lib/core/constants.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const URL_TEMPLATE =
|
|
2
|
-
"https://v${index}.maptrip.de/?tilex=${x}&tiley=${y}&acao=true&context=
|
|
2
|
+
"https://v${index}.maptrip.de/?tilex=${x}&tiley=${y}&acao=true&context=level${z}&project=${project}&projection=wgs84";
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
export const PROVIDER_PROJECTS: { [key: string]: string } = {
|
|
@@ -18,13 +18,16 @@ export const PROVIDER_NAMES: { [key: string]: string } = {
|
|
|
18
18
|
|
|
19
19
|
export const PROVIDER_TRAFFIC: { [key: string]: string | null } = {
|
|
20
20
|
tomtom:
|
|
21
|
-
"https://v0.maptrip.de/tiles?project=TrafficMobileTomTom_EUROPE&tilex=${x}&tiley=${y}&acao=true&context=
|
|
22
|
-
here: "https://v0.maptrip.de/tiles?project=TrafficMobileHere_EUROPE&tilex=${x}&tiley=${y}&acao=true&context=
|
|
23
|
-
openstreetmap:
|
|
21
|
+
"https://v0.maptrip.de/tiles?project=TrafficMobileTomTom_EUROPE&tilex=${x}&tiley=${y}&acao=true&context=traffic_${z}&projection=wgs84",
|
|
22
|
+
here: "https://v0.maptrip.de/tiles?project=TrafficMobileHere_EUROPE&tilex=${x}&tiley=${y}&acao=true&context=traffic_${z}&projection=wgs84",
|
|
23
|
+
openstreetmap: "https://v0.maptrip.de/tiles?project=TrafficMobileOSM_EUROPE&tilex=${x}&tiley=${y}&acao=true&context=traffic_${z}&projection=wgs84",
|
|
24
|
+
orbis: "https://v0.maptrip.de/tiles?project=TrafficMobileORBIS_EUROPE&tilex=${x}&tiley=${y}&acao=true&context=traffic_${z}&projection=wgs84",
|
|
24
25
|
};
|
|
25
26
|
|
|
27
|
+
export const DEFAULT_PROVIDER = "OpenStreetmap";
|
|
28
|
+
|
|
26
29
|
export const MIN_ZOOM = 2;
|
|
27
|
-
export const MAX_ZOOM =
|
|
30
|
+
export const MAX_ZOOM = 21;
|
|
28
31
|
|
|
29
32
|
export const ATTRIBUTION =
|
|
30
33
|
'© <a href="https://maptrip.de/" target="_blank">infoware GmbH</a>';
|