Code example
import { useEffect, useMemo } from 'react';
import * as THREE from 'three';
import { createGeoPoint, createMapCameraPosition } from '@mapconductor/js-sdk-core';
import { useMapLoaded } from '@mapconductor/js-sdk-react';
import { MapLibreDesign, MapLibreMapView2D, useMapLibreViewState } from '@mapconductor/react-for-maplibre';
// (1) Create the map
const initialCamera = createMapCameraPosition({
position: createGeoPoint({ latitude: 35.6812, longitude: 139.7671 }),
zoom: 12,
});
const mapViewState = useMapLibreViewState({
mapDesignType: MapLibreDesign.OsmBrightJa,
cameraPosition: initialCamera,
});
// (2) Prepare the data and interaction state
const position = useMemo(() => createGeoPoint({
latitude: 35.6812,
longitude: 139.7671,
}), []);
// (3) Render the map and its overlays
function ThreeMapObject({ mapViewState, position }) {
const isMapLoaded = useMapLoaded();
useEffect(() => {
if (!isMapLoaded) return;
const holder = mapViewState.getMapViewHolder();
if (!holder) return;
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(0, 1, 1, 0, 0.1, 1000);
camera.position.z = 200;
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
const width = holder.mapView.clientWidth;
const height = holder.mapView.clientHeight;
renderer.setSize(width, height);
camera.right = width;
camera.top = height;
camera.updateProjectionMatrix();
renderer.domElement.style.position = 'absolute';
renderer.domElement.style.inset = '0';
holder.mapView.appendChild(renderer.domElement);
const object = new THREE.Mesh(
new THREE.TorusKnotGeometry(13, 4),
new THREE.MeshNormalMaterial(),
);
scene.add(object);
let frame = 0;
const draw = () => {
const offset = holder.toScreenOffset(position);
if (offset && !(offset instanceof Promise)) {
object.position.set(offset.x, height - offset.y, 0);
}
object.rotation.y += 0.02;
renderer.render(scene, camera);
frame = requestAnimationFrame(draw);
};
draw();
return () => {
cancelAnimationFrame(frame);
renderer.domElement.remove();
renderer.dispose();
};
}, [isMapLoaded, mapViewState, position]);
return null;
}
<MapLibreMapView2D state={mapViewState}>
<ThreeMapObject mapViewState={mapViewState} position={position} />
</MapLibreMapView2D>
How the code works
Render a transparent Three.js canvas over the map and anchor its 3D object with the provider-independent MapViewHolder.toScreenOffset() projection.
useMapLoaded holds the effect back until the map exists, then getMapViewHolder() returns a holder whose WebGL canvas is appended over the map.
On each frame holder.toScreenOffset(position) converts the geographic coordinate into a pixel offset, so the 3D object stays pinned while the map moves — the same call on every provider.