import 'svelte-maplibre-gl/vite';
import { MapLibre, CustomLayer, GlobeControl, Projection } from 'svelte-maplibre-gl';
import * as maplibregl from 'maplibre-gl';
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const earthRadius = 6_371_008.8;
// Helper to calculate matrix for Mercator projection
function getMercatorModelMatrix(origin: maplibregl.LngLatLike, altitude: number) {
const modelAsMercatorCoordinate = maplibregl.MercatorCoordinate.fromLngLat(origin, altitude);
const scale = modelAsMercatorCoordinate.meterInMercatorCoordinateUnits();
return new THREE.Matrix4()
.makeTranslation(modelAsMercatorCoordinate.x, modelAsMercatorCoordinate.y, modelAsMercatorCoordinate.z)
.multiply(new THREE.Matrix4().makeRotationZ(Math.PI))
.multiply(new THREE.Matrix4().makeRotationX(Math.PI / 2))
.scale(new THREE.Vector3(-scale, scale, scale));
}
// Helper to calculate matrix for Globe projection
function getGlobeModelMatrix(origin: maplibregl.LngLatLike, altitude: number) {
const { lng, lat } = maplibregl.LngLat.convert(origin);
const scale = 1 / earthRadius;
return new THREE.Matrix4()
.makeRotationY((lng / 180) * Math.PI)
.multiply(new THREE.Matrix4().makeRotationX((-lat / 180) * Math.PI))
.multiply(new THREE.Matrix4().makeTranslation(0, 0, 1 + altitude / earthRadius))
.multiply(new THREE.Matrix4().makeRotationX(Math.PI / 2))
.scale(new THREE.Vector3(scale, scale, scale));
}
class CustomLayerImpl implements Omit<maplibregl.CustomLayerInterface, 'id' | 'type'> {
renderingMode = '3d' as const;
private camera = new THREE.Camera();
private scene = new THREE.Scene();
private renderer: THREE.WebGLRenderer | null = null;
private map: maplibregl.Map | null = null;
onAdd(map: maplibregl.Map, gl: WebGL2RenderingContext) {
this.map = map;
// Lighting
const directionalLight1 = new THREE.DirectionalLight(0xffffff);
directionalLight1.position.set(0, -70, 100).normalize();
this.scene.add(directionalLight1);
const directionalLight2 = new THREE.DirectionalLight(0xffffff);
directionalLight2.position.set(0, 70, 100).normalize();
this.scene.add(directionalLight2);
// Load GLTF Model
const loader = new GLTFLoader();
loader.load('https://maplibre.org/maplibre-gl-js/docs/assets/34M_17/34M_17.gltf', (gltf) => {
this.scene.add(gltf.scene);
});
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true
});
this.renderer.autoClear = false;
}
render(_gl: WebGL2RenderingContext | WebGLRenderingContext, args: maplibregl.CustomRenderMethodInput) {
const modelOrigin: [number, number] = [148.9819, -35.39847];
const modelAltitude = 0;
const scaling = 10_000.0;
const m = new THREE.Matrix4().fromArray(args.defaultProjectionData.mainMatrix);
// Switch matrix based on projection transition
const l = args.defaultProjectionData.projectionTransition > 0
? getGlobeModelMatrix(modelOrigin, modelAltitude)
: getMercatorModelMatrix(modelOrigin, modelAltitude);
l.scale(new THREE.Vector3(scaling, scaling, scaling));
this.camera.projectionMatrix = m.multiply(l);
this.renderer!.resetState();
this.renderer!.render(this.scene, this.camera);
this.map!.triggerRepaint();
}
}
const customLayerImpl = new CustomLayerImpl();