@maptrip/map-tiles 1.5.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.
@@ -5,7 +5,8 @@ export declare class MapReleaseFetcher {
5
5
  private _osm;
6
6
  private _here;
7
7
  constructor(token: string);
8
- getRelease(provider: string): Promise<string | null | undefined>;
9
- fetchAndExtractRelease(provider: string, url: string): Promise<string | null>;
8
+ getRelease(provider?: string): Promise<string | null>;
9
+ private getUrl;
10
+ fetchAndExtractRelease(provider: string): Promise<string | null>;
10
11
  private extractTargetString;
11
12
  }
@@ -0,0 +1,4 @@
1
+ declare class VectorTileUtils {
2
+ static getAttribution(token: string, provider?: string, attribution?: string, releaseDate?: boolean): Promise<string>;
3
+ }
4
+ export { VectorTileUtils };
@@ -1,20 +1,19 @@
1
1
  import L from "leaflet";
2
2
  import { VectorTileFactory } from "../../core/VectorTileFactory.js";
3
- import { MIN_ZOOM, MAX_ZOOM, ATTRIBUTION } from "../../core/constants.js";
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
- MIN_ZOOM = MIN_ZOOM;
10
- MAX_ZOOM = MAX_ZOOM;
9
+ static attributions: string[] = [];
11
10
  token: string;
12
11
  factory: VectorTileFactory;
13
- releaseFetcher: MapReleaseFetcher;
14
- provider: string;
15
- attribution: string;
16
- release: string;
17
- releaseDate: boolean;
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(options as L.GridLayerOptions);
34
- this.token = options.token!;
35
- this.provider = options.provider
36
- ? options.provider?.toLowerCase()
37
- : "openstreetmap";
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
- //@ts-expect-error picking up LayerOptions rather than GridLayerOptions
59
- this.options.minZoom = options.minZoom
60
- ? Math.max(this.MIN_ZOOM, options.minZoom)
61
- : this.MIN_ZOOM;
62
- //@ts-expect-error
63
- this.options.maxZoom = options.maxZoom
64
- ? Math.min(this.MAX_ZOOM, options.maxZoom)
65
- : this.MAX_ZOOM;
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
- createTile(coords: PointXY) {
71
- return this.factory.getJsonTile(coords.z!, coords.x, coords.y, this.token);
55
+ override onAdd(map: L.Map): this {
56
+ this.attachedMap = map;
57
+ return super.onAdd(map);
72
58
  }
73
59
 
74
- private async initializeAttribution() {
75
- await this.setRelease();
76
- await this.setAttribution();
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
- // Set an original release
80
- private async setRelease() {
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 async setAttribution() {
88
- if (!this._map) return;
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
- // remove the original attribution in order to append the release date
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._map.attributionControl.removeAttribution(this.attribution);
77
+ const map = this.attachedMap;
78
+ const control = map?.attributionControl;
117
79
 
118
- //@ts-expect-error doesn't recognize provider in options object
119
- const eventProvider = event.layer.options.provider;
120
- let newProvider: string;
121
- if (eventProvider) {
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
- this.provider = newProvider;
85
+ // set attribution and update control
86
+ this.options.attribution = attribution;
135
87
 
136
- await this.setAttribution();
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: "OpenStreetmap",
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
 
@@ -138,7 +142,7 @@ class VectorTileRenderer {
138
142
  );
139
143
  }
140
144
  }
141
-
145
+
142
146
  drawErroredTile(canvas: HTMLCanvasElement): void {
143
147
  var context = canvas.getContext("2d");
144
148
  if (context != null) {
@@ -452,11 +456,11 @@ class VectorTileRenderer {
452
456
  }
453
457
 
454
458
  const width = boundaries.max.lon - boundaries.min.lon;
455
- const height = boundaries.max.lat - boundaries.min.lat;
459
+ const height = boundaries.max.lat - boundaries.min.lat;
456
460
  if (width === 0 || height === 0) {
457
461
  return;
458
462
  }
459
-
463
+
460
464
  var points: { x: number; y: number }[] = [];
461
465
 
462
466
  for (let k = 0; k < part.length; k++) {
@@ -650,6 +654,37 @@ class VectorTileRenderer {
650
654
  return points;
651
655
  }
652
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
+
653
688
  /**
654
689
  * @private
655
690
  */
@@ -664,9 +699,15 @@ class VectorTileRenderer {
664
699
  var merged = { ...VectorTileRenderer.DEFAULT_STYLE.LINE, ...style };
665
700
  context.globalAlpha = merged.opacity;
666
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
+
667
709
  if ("linesOutline" === phase) {
668
710
  if (merged.outlineColor && merged.outlineWidth) {
669
- let points = this.projectCoordinates(coordinates, boundaries);
670
711
  this.drawPath(
671
712
  points,
672
713
  merged.outlineColor,
@@ -678,7 +719,6 @@ class VectorTileRenderer {
678
719
  return;
679
720
  }
680
721
 
681
- let points = this.projectCoordinates(coordinates, boundaries);
682
722
  this.drawPath(points, merged.color, merged.width, context);
683
723
 
684
724
  if (merged.dashColor && merged.dashLength) {
@@ -724,7 +764,7 @@ class VectorTileRenderer {
724
764
 
725
765
  const x: number = points[i].x + f * dx;
726
766
  const y: number = points[i].y + f * dy;
727
-
767
+
728
768
  const size: number = Math.max(12, 1.5 * merged.width);
729
769
 
730
770
  if (x > size && y > size && x < VECTOR_TILE_SIZE - size && y < VECTOR_TILE_SIZE - size) {
@@ -1009,10 +1049,7 @@ class VectorTileRenderer {
1009
1049
 
1010
1050
  context.strokeStyle = color;
1011
1051
  context.lineWidth = width;
1012
-
1013
- context.lineCap = "square";
1014
- context.lineJoin = "miter";
1015
-
1052
+
1016
1053
  context.moveTo(points[0].x, points[0].y);
1017
1054
  for (var j = 1; j < points.length; j++) {
1018
1055
  context.lineTo(points[j].x, points[j].y);
@@ -1166,7 +1203,7 @@ class VectorTileRenderer {
1166
1203
  if (text) {
1167
1204
  let merged = { ...VectorTileRenderer.DEFAULT_STYLE.TEXT, ...text };
1168
1205
  let font = `${merged.fontStyle} ${merged.fontWeight} ${merged.size}px ${merged.font}`.trim();
1169
-
1206
+
1170
1207
  result.push(font);
1171
1208
  }
1172
1209
  }
@@ -1196,6 +1233,7 @@ class VectorTileRenderer {
1196
1233
  dashColor: undefined,
1197
1234
  dashLength: 0,
1198
1235
  opacity: 1.0,
1236
+ delta: 0,
1199
1237
  },
1200
1238
  IMAGE: {
1201
1239
  url: undefined,
@@ -1,5 +1,5 @@
1
1
  export const URL_TEMPLATE =
2
- "https://v${index}.maptrip.de/?tilex=${x}&tiley=${y}&acao=true&context=tomtom${z}&project=${project}&projection=wgs84";
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=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: null,
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 = 18;
30
+ export const MAX_ZOOM = 21;
28
31
 
29
32
  export const ATTRIBUTION =
30
33
  '&#169;&nbsp;<a href="https://maptrip.de/" target="_blank">infoware GmbH</a>';