Maquette

Overview

Maquette turns a site outline into a 3D model you can import into your favourite editor or viewer.


Getting started

npm install @maquette/client @maquette/three
import { Canvas } from '@react-three/fiber'
import { maquette } from '@maquette/client'
import { MaquetteLoader } from '@maquette/three'

const model = await maquette.build({
  outline: [
    [-71.2045, 46.8118],
    [-71.2005, 46.8112],
    [-71.1998, 46.8135],
    [-71.2030, 46.8146],
    [-71.2052, 46.8134],
  ],
})

const site = new MaquetteLoader().parse(model)

export default function App() {
  return (
    <Canvas>
      <primitive object={site} />
    </Canvas>
  )
}

Adding textures

Build with bare ground, then drape any imagery you have the right to display. Esri World Imagery needs no API key; show the credit it asks for.

// The document is georeferenced, so any imagery service can dress the ground.
const [minX, minY, , maxX, maxY] = model.metadata.geographicalExtent
const zone = model.metadata.referenceSystem.split('/').pop()

const texture = new TextureLoader().load(
  'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/export' +
    `?bbox=${minX},${minY},${maxX},${maxY}&bboxSR=${zone}&imageSR=${zone}` +
    '&size=1024,1024&format=jpg&f=image',
)
texture.colorSpace = SRGBColorSpace

const width = maxX - minX
const depth = maxY - minY
site.traverse((child) => {
  if (child.userData.type !== 'TINRelief') return
  const position = child.geometry.getAttribute('position')
  const uvs = new Float32Array(position.count * 2)
  for (let i = 0; i < position.count; i++) {
    uvs[i * 2] = (position.getX(i) + width / 2) / width
    uvs[i * 2 + 1] = (position.getY(i) + depth / 2) / depth
  }
  child.geometry.setAttribute('uv', new BufferAttribute(uvs, 2))
  child.material = new MeshStandardMaterial({ map: texture })
})