# geo
The city frame the `helsinki` package's tiles are modelled in, and the conversions into it.
- Frame: engine Y-up metres, `X = E - ORIGIN_E`, `Y` = N2000 height, `Z = -(N - ORIGIN_N)`;
`ORIGIN_E` / `ORIGIN_N` are the GK25 coordinates of the origin, `SEA_LEVEL_Y` the water.
- `fromGK25(E, N, h)` / `toGK25(x, y, z)` and `fromWGS84(lat, lon, h)` / `toWGS84(x, y, z)`, the
latter a Krüger-series transverse Mercator with the ETRS-GK25 parameters (EPSG:3879).
- `TILES`, `tileBounds(key)`, `tileAt(x, z)`, `coverage()`: the 2 km sheets the package ships.
- `LANDMARKS`, `landmark(name)`, `landmarks()`: named places with frame positions.
- `distance(x1, z1, x2, z2)` and `bearing(x1, z1, x2, z2)` on the ground.
```luau
local geo = require("helsinki.geo")
local x, _, z = geo.fromWGS84(60.1703, 24.9522) -- Helsinki Cathedral
```
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# helsinki
The real Helsinki, as the city publishes it in its own 3D map: the City of Helsinki's 3D city model
over the peninsula from Pasila in the north to Suomenlinna in the south, eleven 2 km sheets, all
in one shared frame so they stand next to each other with no placement work. Each sheet is the
city's terrain model draped with the 2025 orthophoto, and its textured LOD2 building models on top
of it, the same data https://kartta.hel.fi/3d/ draws. Every building carries its address, storeys,
year of construction and use.
## Contents
- `tiles/ground_<key>.bundle/` — the terrain of one 2 km GK25 map sheet (about 37 k triangles) with
the 2025 orthophoto as its texture: 4096 px (0.5 m per pixel) on the four centre sheets, 2048 px
on the seven outer ones. Water is in the photograph, at sea level in the terrain.
- `tiles/buildings_<key>.bundle/` — the sheet's LOD2 building models (12 k to 310 k triangles) with
their facade and roof photographs packed into one atlas; buildings the city model leaves
untextured are their flat light grey. `<key>` is `NNNEEE`: N = 6 NNN 000 m, E = 25 EEE 000 m in
ETRS-GK25.
- `data/buildings_<key>.module/` — the sheet's building register: for every building its id,
frame position and roof height, and the city's attributes (`Address`, `storeysAboveGround`,
`yearOfConstruction`, `measuredHeight`, `function`, `usage`).
- `HelsinkiCity.component/` — stands the city up under one entity: ground and buildings per sheet
as `Asset` children, a sea beyond the sheets, optional mesh collision, shadow control, and queries:
`groundHeight(x, z)`, `surfaceHeight(x, z)`, `landmark(name)`, `landmarks()`, `tileEntities()`,
`tileStatus(key)`, `coverage()`.
- `buildings.module/` — the register over all sheets: `sheet(key)`, `all()`, `nearest(x, z)`,
`find(street, number)`, `filter(pred)`.
- `geo.module/` — the frame and the geodesy: `ORIGIN_E` / `ORIGIN_N`, `fromGK25` / `toGK25`,
`fromWGS84` / `toWGS84` (a Krüger-series transverse Mercator, sub-millimetre inside the sheets),
the sheet index (`TILES`, `tileBounds`, `tileAt`, `coverage`), the landmark table and
`landmark(name)`, plus `distance` and `bearing` on the ground.
- `helsinki.guide/` — the working guide: the frame, what each sheet covers, placing things by
address or coordinate, and where the data comes from.
## The frame
Engine Y-up, metres, right-handed. `X = E - 25 496 000` (east), `Y` = height above N2000,
`Z = -(N - 6 673 000)` (north is -Z). The origin is in the city centre near the Central
station, so the peninsula lies within about 4 km of it: the sheet set spans X -2000..4000 and
Z -5000..3000. Sea level is `geo.SEA_LEVEL_Y` (-0.2 m).
## Usage
```luau
-- Stand the whole city up (every sheet in the package, with the sea):
local city = entity.spawn("Helsinki", { components = { HelsinkiCity = {} } })
-- Or only the centre, ground alone, as a map to look at with no physics cost:
entity.spawn("Helsinki", { components = { HelsinkiCity = { tiles = "672496,672498", buildings = false, collision = false } } })
-- Where is something? The component answers in the frame, with the terrain height:
local c = city.component.get("HelsinkiCity")
local cathedral = c:landmark("Helsinki Cathedral") -- { x, y, z, lat, lon, kind, district }
local roof = c:surfaceHeight(cathedral.x, cathedral.z) -- the top of whatever stands there
-- Which building is that, and who lives at an address?
local buildings = require("helsinki.buildings")
local b = buildings.nearest(cathedral.x, cathedral.z) -- { id, x, y, z, top, Address, storeysAboveGround, yearOfConstruction, ... }
local esplanadi = buildings.find("Eteläesplanadi", "2")
-- Any coordinate you have converts the same way:
local geo = require("helsinki.geo")
local x, _, z = geo.fromWGS84(60.1719, 24.9414) -- Central station
```
The sheets stand as temporary children, so a saved scene holds only the entity carrying
`HelsinkiCity` and stands the city up again on load. A sheet whose bundles have not arrived yet
(an install still in progress) is retried every second until it stands; `missingTiles()` lists
them and `tileStatus(key)` says which part is still arriving.
## Notes
- The orthophoto is the aerial picture: the sun and the shadows of the flight day are in it. A
low sun in the scene from the same side, or `castsShadows = false`, reads as one picture.
- Facade textures are the city model's own photographs, packed per sheet; they read from a few
tens of metres and up, which is the height a map or a board is seen from.
- Landmark positions come from public coordinates and are good to a few tens of metres; the
building register's positions are the model's own.
- Data: City of Helsinki 3D city model (buildings LOD2 with textures, terrain 2021) and
orthophoto 2025, CC BY 4.0. Attribution: "Helsinki 3D city model, City of Helsinki".
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).
# Mesh
Mesh asset. `data.zmsh` is the engine-native geometry payload (`renderer.mesh.encode`); consumers reference the asset's guid.
# Texture
Managed texture asset. `data.ztex` is the encoded payload the renderer uploads; compression settings live in the `.metadata` `settings` block (edit to re-encode).