Projection and zoom
Projection flattens a round Earth onto a flat screen; zoom says how close you are. Providers disagree on both, so MapConductor shares one set of constants and formulas across all three platforms and normalises every value it exposes. The camera page covers how to drive the camera — this one covers the numbers underneath.
01 · The Earth constants
Projection, geodesy and tile maths all read from Earth.
02 · Projections
A projection has exactly two operations: project and unproject — flatten a coordinate to a planar Offset and take it back again. Two implementations ship with the SDK.
Web Mercator, in metres
Projects onto the ±20,037,508.34 m plane, the same as EPSG:3857. Used for tile-boundary maths and for converting screen distances into metres.
256-pixel tile space
Projects into the coordinate space of the single zoom-0 tile (256×256). This is the space the tile renderers draw in.
interface ProjectionInterface {
fun project(position: GeoPointInterface): Offset
fun unproject(point: Offset): GeoPointInterface
}
val meters = WebMercator.project(tokyo) // x, y はメートル
val tile = WGS84.project(tokyo) // 0..256 のタイル座標// iOS では投影はタイル・空間インデックス側の内部実装として持ちます。 // アプリからの座標 ⇄ 画面の変換は MapViewHolder を使います。 let offset = holder.toScreenOffset(position: tokyo) let point = holder.fromScreenOffsetSync(offset: tapLocation)
import { WebMercator, WGS84, Earth } from '@mapconductor/js-sdk-core';
const meters = WebMercator.project(tokyo); // { x, y } はメートル
const back = WebMercator.unproject(meters);
const tile = WGS84.project(tokyo); // 0..256 のタイル座標When you need pixels rather than a plane, use toScreenOffset / fromScreenOffset on the MapViewHolder instead of a projection — those account for the tilt and bearing actually on screen.
03 · Zoom and altitude
2D tile providers express scale as a zoom level; 3D ones as camera altitude or range. MapConductor converts between them so your app always sees one zoom scale. Each provider implements one converter class extending the shared abstract base.
Latitude and tilt are part of the conversion: Web Mercator magnifies towards the poles, so the same zoom level needs a several-fold different camera altitude at the equator and at high latitude.
abstract class AbstractZoomAltitudeConverter(
protected val zoom0Altitude: Double,
) {
abstract fun zoomLevelToAltitude(zoomLevel: Double, latitude: Double, tilt: Double): Double
abstract fun altitudeToZoomLevel(altitude: Double, latitude: Double, tilt: Double): Double
}public protocol ZoomAltitudeConverterProtocol {
var zoom0Altitude: Double { get }
func zoomLevelToAltitude(zoomLevel: Double, latitude: Double, tilt: Double) -> Double
func altitudeToZoomLevel(altitude: Double, latitude: Double, tilt: Double) -> Double
}export abstract class AbstractZoomAltitudeConverter {
constructor(protected readonly zoom0Altitude: number =
AbstractZoomAltitudeConverter.DEFAULT_ZOOM0_ALTITUDE) {}
abstract zoomLevelToAltitude(params: { zoomLevel: number; latitude: number; tilt: number }): number;
abstract altitudeToZoomLevel(params: { altitude: number; latitude: number; tilt: number }): number;
}Each provider package ships its own ZoomAltitudeConverter. Your app does not normally call it, but it is available if you are building your own 3D presentation.
// The same camera position handed to both states
val shared = MapCameraPosition(
position = GeoPoint.fromLatLong(35.6812, 139.7671),
zoom = 14.0,
)
val maplibre = rememberMapLibreMapViewState(cameraPosition = shared)
val googlemaps = rememberGoogleMapViewState(cameraPosition = shared)
// Move one and the same value goes to the other
Row {
MapLibreMapView(
state = maplibre,
modifier = Modifier.weight(1f),
onCameraMoveEnd = { googlemaps.moveCameraTo(it) },
)
GoogleMapView(
state = googlemaps,
modifier = Modifier.weight(1f),
onCameraMoveEnd = { maplibre.moveCameraTo(it) },
)
}04 · Limits
These are the constants the conversion works with. Lower bounds on the cosines keep the formulas from blowing up near the poles or looking straight down.
The zoom-0 reference altitude was not derived on paper: it is the result of measuring the area each engine actually shows at a given zoom and tuning until they matched. The only constants are that relation and one step of zoom halving the altitude (ZOOM_FACTOR = 2).