Skip to content

Commit 468b890

Browse files
committed
[POC] MapLibre layer
replaces all BG layers with a VectorTile equivalent switch to mercator as default projection to make it happen (not possible yet to mix projection systems, needs some work done on the geoblocks/ol-maplibre-layer library) fix INT staging URL : no sys service serves theses styles yet update to MapLibre 5.0.0
1 parent 3604345 commit 468b890

18 files changed

Lines changed: 526 additions & 118 deletions

packages/geoadmin-coordinates/src/proj/StandardCoordinateSystem.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ export default abstract class StandardCoordinateSystem extends CoordinateSystem
1717
}
1818

1919
getDefaultZoom(): number {
20-
return STANDARD_ZOOM_LEVEL_1_25000_MAP
20+
return this.get1_25000ZoomLevel()
2121
}
2222
}

packages/geoadmin-coordinates/src/proj/SwissCoordinateSystem.ts

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
import { closest, round } from '@geoadmin/numbers'
22

33
import {
4+
PIXEL_LENGTH_IN_KM_AT_ZOOM_ZERO_WITH_256PX_TILES,
45
type ResolutionStep,
56
STANDARD_ZOOM_LEVEL_1_25000_MAP,
67
SWISS_ZOOM_LEVEL_1_25000_MAP,
78
} from '@/proj/CoordinateSystem'
89
import CustomCoordinateSystem from '@/proj/CustomCoordinateSystem'
910

11+
/**
12+
* Latitude where the LV95 plane is anchored to the Mercator system. Used to calculate/transform
13+
* LV95 zoom level into Mercator zoom level
14+
*
15+
* Value can be found in the PROJ4 matrix on epsg.io
16+
*
17+
* @see https://epsg.io/2056
18+
*/
19+
const LV95_LATITUDE_CENTER_IN_WGS84: number = 46.9524055555556
20+
1021
/**
1122
* Resolutions for each LV95 zoom level, from 0 to 14
1223
*
@@ -163,30 +174,47 @@ export default class SwissCoordinateSystem extends CustomCoordinateSystem {
163174
* level to show the 1:25'000 map if the input is invalid
164175
*/
165176
transformCustomZoomLevelToStandard(customZoomLevel: number): number {
166-
const key = Math.floor(customZoomLevel)
167-
if (SWISSTOPO_TILEGRID_ZOOM_TO_STANDARD_ZOOM_MATRIX.length - 1 >= key) {
168-
return SWISSTOPO_TILEGRID_ZOOM_TO_STANDARD_ZOOM_MATRIX[key]
169-
}
170-
// if no matching zoom level was found, we return the one for the 1:25'000 map
171-
return STANDARD_ZOOM_LEVEL_1_25000_MAP
177+
const lv95Resolution: number = this.getResolutionForZoomAndCenter(customZoomLevel)
178+
// reverting formula from https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Resolution_and_Scale
179+
return Math.log2(
180+
1.0 /
181+
(lv95Resolution /
182+
PIXEL_LENGTH_IN_KM_AT_ZOOM_ZERO_WITH_256PX_TILES /
183+
Math.cos((Math.PI * LV95_LATITUDE_CENTER_IN_WGS84) / 180.0))
184+
)
172185
}
173186

174187
getResolutionForZoomAndCenter(zoom: number): number {
175188
// ignoring the center, as it won't have any effect on the chosen zoom level
176-
return LV95_RESOLUTIONS[Math.round(zoom)]
189+
const roundedZoom: number = Math.floor(zoom)
190+
const resolutions: ResolutionStep[] = this.getResolutionSteps()
191+
const resolutionMatchingZoom: ResolutionStep | undefined = resolutions.find(
192+
(step) => step.zoom === roundedZoom
193+
)
194+
if (resolutionMatchingZoom) {
195+
const nextStep: ResolutionStep | undefined = resolutions.find(
196+
(step) => step.zoom === roundedZoom + 1
197+
)
198+
if (!nextStep) {
199+
return resolutionMatchingZoom.resolution
200+
}
201+
const zoomFactor: number = resolutionMatchingZoom.resolution / nextStep.resolution
202+
return resolutionMatchingZoom.resolution / Math.pow(zoomFactor, zoom % 1.0)
203+
}
204+
return LV95_RESOLUTIONS[roundedZoom]
177205
}
178206

179207
getZoomForResolutionAndCenter(resolution: number): number {
180208
// ignoring the center, as it won't have any effect on the resolution
181-
const matchingResolution = LV95_RESOLUTIONS.find(
182-
(lv95Resolution) => lv95Resolution <= resolution
183-
)
184-
if (matchingResolution) {
185-
return LV95_RESOLUTIONS.indexOf(matchingResolution)
209+
const matchingResolutionStep: ResolutionStep | undefined = this.getResolutionSteps()
210+
.filter((step) => step.zoom)
211+
.find((step) => step.resolution <= resolution)
212+
if (matchingResolutionStep && matchingResolutionStep.zoom !== undefined) {
213+
return matchingResolutionStep.zoom
186214
}
187215
// if no match was found, we have to decide if the resolution is too great,
188216
// or too small to be matched and return the zoom accordingly
189-
const smallestResolution = LV95_RESOLUTIONS.slice(-1)[0]
217+
const smallestResolution: number = LV95_RESOLUTIONS.slice(-1)[0]
190218
if (smallestResolution > resolution) {
191219
// if the resolution was smaller than the smallest available, we return the zoom level corresponding
192220
// to the smallest available resolution

packages/geoadmin-coordinates/src/proj/WebMercatorCoordinateSystem.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ export default class WebMercatorCoordinateSystem extends StandardCoordinateSyste
4040
* resolution = 156543.03 meters / pixel * cos(latitude) / (2 ^ zoom level)
4141
*/
4242
getResolutionForZoomAndCenter(zoom: number, center: SingleCoordinate): number {
43-
const centerInRad = proj4(this.epsg, WGS84.epsg, center).map(
43+
const centerInRad: SingleCoordinate = proj4(this.epsg, WGS84.epsg, center).map(
4444
(coordinate) => (coordinate * Math.PI) / 180.0
45-
)
45+
) as SingleCoordinate
4646
return round(
4747
Math.abs(
4848
(PIXEL_LENGTH_IN_KM_AT_ZOOM_ZERO_WITH_256PX_TILES * Math.cos(centerInRad[1])) /

packages/geoadmin-coordinates/src/proj/__test__/CoordinateSystem.class.spec.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest'
33
import { LV95, WEBMERCATOR, WGS84 } from '@/proj'
44
import CoordinateSystemBounds from '@/proj/CoordinateSystemBounds'
55
import StandardCoordinateSystem from '@/proj/StandardCoordinateSystem'
6+
import { LV95_RESOLUTIONS } from '@/proj/SwissCoordinateSystem'
67

78
class BoundlessCoordinateSystem extends StandardCoordinateSystem {
89
constructor() {
@@ -55,4 +56,45 @@ describe('CoordinateSystem', () => {
5556
})
5657
// remaining test for this function are handled in the CoordinateSystemBounds.class.spec.js file
5758
})
59+
describe('getResolutionSteps', () => {
60+
it('returns all standard (Mercator) resolutions', () => {
61+
const resolutions = WEBMERCATOR.getResolutionSteps()
62+
expect(resolutions).to.be.an('Array').lengthOf(21)
63+
64+
// mashup of values from https://wiki.openstreetmap.org/wiki/Zoom_levels (from zoom 0 to 18)
65+
// and https://wiki.openstreetmap.org/wiki/Zoom_levels (zoom 19 and 20)
66+
const expectedResolutions = [
67+
156543.03, 78271.52, 39135.76, 19567.88, 9783.94, 4891.97, 2445.98, 1222.99, 611.5,
68+
305.75, 152.87, 76.437, 38.219, 19.109, 9.5546, 4.7773, 2.3887, 1.1943, 0.5972,
69+
0.299, 0.149,
70+
]
71+
72+
resolutions.forEach((resolutionStep, index) => {
73+
expect(resolutionStep).to.be.an('Object')
74+
expect(resolutionStep.zoom).to.eq(index)
75+
expect(resolutionStep.resolution).to.be.greaterThan(0)
76+
77+
// see https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Resolution_and_Scale
78+
// the formula that was used is : resolution = 156543.03 meters/pixel * cos(latitude) / (2 ^ zoomlevel)
79+
// with latitude being 47° (about Bern's latitude)
80+
expect(
81+
resolutionStep.resolution,
82+
`zoom level ${index} resolution is wrongly calculated`
83+
).to.be.closeTo(expectedResolutions[index], 0.01)
84+
})
85+
})
86+
it.skip('returns all LV95 resolutions', () => {
87+
const resolutions = LV95.getResolutionSteps()
88+
expect(resolutions).to.be.an('Array').lengthOf(LV95_RESOLUTIONS.length)
89+
90+
resolutions.forEach((resolutionStep, index) => {
91+
expect(resolutionStep).to.be.an('Object')
92+
expect(resolutionStep.zoom).to.eq(index)
93+
expect(
94+
resolutionStep.resolution,
95+
`wrong LV95 resolution at zoom level ${index}`
96+
).to.be.eq(LV95_RESOLUTIONS[index])
97+
})
98+
})
99+
})
58100
})

packages/mapviewer/.env.development

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ VITE_API_SERVICES_BASE_URL=https://sys-map.dev.bgdi.ch/api/
99
VITE_API_SERVICE_KML_BASE_URL=https://sys-public.dev.bgdi.ch/
1010
VITE_APP_API_SERVICE_SHORTLINK_BASE_URL=https://sys-s.dev.bgdi.ch/
1111
VITE_APP_3D_TILES_BASE_URL=https://sys-3d.dev.bgdi.ch/
12-
VITE_APP_VECTORTILES_BASE_URL=https://sys-verctortiles.dev.bgdi.ch/
12+
VITE_APP_VECTORTILES_BASE_URL=https://vectortiles.geo.admin.ch/
1313
VITE_APP_SERVICE_PROXY_BASE_URL=https://sys-proxy.dev.bgdi.ch/

packages/mapviewer/.env.integration

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ VITE_API_SERVICES_BASE_URL=https://sys-map.int.bgdi.ch/api/
88
VITE_API_SERVICE_KML_BASE_URL=https://sys-public.int.bgdi.ch/
99
VITE_APP_API_SERVICE_SHORTLINK_BASE_URL=https://sys-s.int.bgdi.ch/
1010
VITE_APP_3D_TILES_BASE_URL=https://sys-3d.int.bgdi.ch/
11-
VITE_APP_VECTORTILES_BASE_URL=https://sys-verctortiles.int.bgdi.ch/
11+
VITE_APP_VECTORTILES_BASE_URL=https://vectortiles.geo.admin.ch/
1212
VITE_APP_SERVICE_PROXY_BASE_URL=https://sys-proxy.int.bgdi.ch/

packages/mapviewer/src/api/layers/GeoAdminLayer.class.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export default class GeoAdminLayer extends AbstractLayer {
2222
* layer
2323
* @param {String | null} layerData.idIn3d The layer ID to be used as substitute for this layer
2424
* when we are showing the 3D map. Will be using the same layer if this is set to null.
25+
* @param {String | null} layerData.idInVectorTile The layer ID to be used as substitute for
26+
* this layer when we are showing the map with vector tiles. Will be using the same layer if
27+
* this is set to null.
2528
* @param {String} layerData.technicalName The ID/name to use when requesting the WMS/WMTS
2629
* backend, this might be different than id, and many layers (with different id) can in fact
2730
* request the same layer, through the same technical name, in the end)
@@ -75,6 +78,7 @@ export default class GeoAdminLayer extends AbstractLayer {
7578
type = null,
7679
id = null,
7780
idIn3d = null,
81+
idInVectorTile = null,
7882
technicalName = null,
7983
opacity = 1.0,
8084
visible = true,
@@ -122,6 +126,7 @@ export default class GeoAdminLayer extends AbstractLayer {
122126
this.isHighlightable = isHighlightable
123127
this.topics = topics
124128
this.idIn3d = idIn3d
129+
this.idInVectorTile = idInVectorTile
125130
this.isSpecificFor3D = id.toLowerCase().endsWith('_3d')
126131
this.searchable = searchable
127132
}

packages/mapviewer/src/api/layers/GeoAdminVectorLayer.class.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,26 @@ import { getVectorTilesBaseUrl } from '@/config/baseUrl.config'
1616
*/
1717
export default class GeoAdminVectorLayer extends GeoAdminLayer {
1818
/**
19-
* @param {string} layerId The ID of this layer
20-
* @param {LayerAttribution[]} extraAttributions Extra attribution in case this vector layer is
21-
* a mix of many sources
19+
* @param {string} vtLayerConfig.id The ID of this layer
20+
* @param {string} vtLayerConfig.vectorStyleId The ID of the style in the VT backend
21+
* @param {LayerAttribution[]} vtLayerConfig.extraAttributions Extra attribution in case this
22+
* vector layer is a mix of many sources
2223
*/
23-
constructor(layerId, extraAttributions = []) {
24+
constructor(vtLayerConfig = {}) {
25+
const { id, vectorStyleId = null, extraAttributions = [] } = vtLayerConfig
2426
super({
25-
name: layerId,
27+
id,
28+
name: id,
2629
type: LayerTypes.VECTOR,
2730
baseUrl: getVectorTilesBaseUrl(),
28-
id: layerId,
29-
technicalName: layerId,
31+
technicalName: id,
3032
attributions: [
3133
...extraAttributions,
3234
new LayerAttribution('swisstopo', 'https://www.swisstopo.admin.ch/en/home.html'),
3335
],
3436
isBackground: true,
3537
hasLegend: false,
3638
})
39+
this.vectorStyleId = vectorStyleId
3740
}
3841
}

packages/mapviewer/src/api/layers/GeoAdminWMSLayer.class.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export default class GeoAdminWMSLayer extends GeoAdminLayer {
2121
* @param {String} layerData.id The unique ID of this layer
2222
* @param {String | null} layerData.idIn3d The layer ID to be used as substitute for this layer
2323
* when we are showing the 3D map. Will be using the same layer if this is set to null.
24+
* @param {String | null} layerData.idInVectorTile The layer ID to be used as substitute for
25+
* this layer when we are showing the map with vector tiles. Will be using the same layer if
26+
* this is set to null.
2427
* @param {String} layerData.technicalName The ID/name to use when requesting the WMS backend,
2528
* this might be different than id, and many layers (with different id) can in fact request
2629
* the same layer, through the same technical name, in the end)
@@ -69,6 +72,7 @@ export default class GeoAdminWMSLayer extends GeoAdminLayer {
6972
name = null,
7073
id = null,
7174
idIn3d = null,
75+
idInVectorTile = null,
7276
technicalName = null,
7377
opacity = 1.0,
7478
visible = true,
@@ -91,6 +95,7 @@ export default class GeoAdminWMSLayer extends GeoAdminLayer {
9195
type: LayerTypes.WMS,
9296
id,
9397
idIn3d,
98+
idInVectorTile,
9499
technicalName,
95100
opacity,
96101
visible,

packages/mapviewer/src/api/layers/GeoAdminWMTSLayer.class.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export default class GeoAdminWMTSLayer extends GeoAdminLayer {
2323
* @param {String} layerData.id Unique layer ID
2424
* @param {String | null} layerData.idIn3d The layer ID to be used as substitute for this layer
2525
* when we are showing the 3D map. Will be using the same layer if this is set to null.
26+
* @param {String | null} layerData.idInVectorTile The layer ID to be used as substitute for
27+
* this layer when we are showing the map with vector tiles. Will be using the same layer if
28+
* this is set to null.
2629
* @param {String} layerData.technicalName ID to be used in our backend (can be different from
2730
* the id)
2831
* @param {Number} [layerData.opacity=1.0] Opacity value between 0.0 (transparent) and 1.0
@@ -64,6 +67,7 @@ export default class GeoAdminWMTSLayer extends GeoAdminLayer {
6467
name = null,
6568
id = null,
6669
idIn3d = null,
70+
idInVectorTile = null,
6771
technicalName = null,
6872
opacity = 1.0,
6973
visible = true,
@@ -90,6 +94,7 @@ export default class GeoAdminWMTSLayer extends GeoAdminLayer {
9094
type: LayerTypes.WMTS,
9195
id,
9296
idIn3d,
97+
idInVectorTile,
9398
technicalName,
9499
opacity,
95100
visible,

0 commit comments

Comments
 (0)