Структура проекта

Структура проекта определяет организацию исходного кода, ресурсов, конфигурационных файлов и внешних зависимостей. В приложениях на CesiumJS этот вопрос особенно важен, поскольку проект обычно содержит большое количество геопространственных данных, 3D-моделей, изображений, шейдеров, пользовательских компонентов интерфейса и модулей визуализации.

Грамотно организованная структура позволяет:

  • упростить поддержку кода;
  • разделить ответственность между компонентами;
  • ускорить масштабирование проекта;
  • уменьшить количество зависимостей между модулями;
  • облегчить командную разработку.

Базовая структура проекта

Типичный проект на CesiumJS может иметь следующую организацию каталогов:

project/
│
├── public/
│   ├── assets/
│   │   ├── images/
│   │   ├── models/
│   │   ├── terrain/
│   │   └── textures/
│   │
│   └── cesium/
│
├── src/
│   ├── core/
│   ├── viewer/
│   ├── layers/
│   ├── entities/
│   ├── primitives/
│   ├── widgets/
│   ├── services/
│   ├── utils/
│   ├── styles/
│   └── main.js
│
├── package.json
├── vite.config.js
└── README.md

Каждый каталог решает отдельную задачу и не должен смешивать различные уровни логики приложения.


Каталог public

Папка public содержит статические ресурсы, которые веб-сервер отдает без обработки сборщиком.

Пример:

public/
├── assets/
├── favicon.ico
└── robots.txt

В контексте CesiumJS здесь обычно размещаются:

  • текстуры;
  • иконки;
  • glTF-модели;
  • 3D Tiles;
  • наборы Terrain;
  • GeoJSON-файлы;
  • KML-файлы.

Например:

public/assets/models/
├── aircraft.glb
├── satellite.glb
└── vehicle.glb

Доступ к таким ресурсам осуществляется по URL:

const modelUrl = "/assets/models/aircraft.glb";

Хранение ресурсов Cesium

Cesium использует собственный набор служебных файлов:

Build/
├── Assets/
├── ThirdParty/
├── Widgets/
└── Workers/

При использовании современных сборщиков данные каталоги копируются в отдельную директорию:

public/cesium/
├── Assets/
├── Widgets/
├── Workers/
└── ThirdParty/

После этого необходимо определить базовый путь:

window.CESIUM_BASE_URL = "/cesium";

Без корректного размещения этих ресурсов не будут работать:

  • загрузчики тайлов;
  • веб-воркеры;
  • встроенные виджеты;
  • часть механизмов рендеринга.

Каталог src

В папке src располагается основной исходный код приложения.

Пример:

src/
├── core/
├── viewer/
├── layers/
├── entities/
├── services/
└── main.js

Все JavaScript-модули должны находиться именно здесь.


Точка входа приложения

Обычно используется файл:

src/main.js

Именно отсюда начинается инициализация приложения.

Пример:

import { createViewer } from "./viewer/createViewer.js";

const viewer = createViewer();

Точка входа должна содержать минимальное количество логики.

Нежелательно создавать объекты сцены непосредственно в этом файле.

Плохой пример:

const viewer = new Cesium.Viewer("cesiumContainer");

viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(
        30,
        50
    )
});

Подобный код быстро становится трудно поддерживать.


Модуль viewer

Создание экземпляра Viewer обычно выносится в отдельный каталог.

Структура:

viewer/
├── createViewer.js
├── configureCamera.js
├── configureScene.js
└── configureWidgets.js

Создание Viewer:

import * as Cesium from "cesium";

export function createViewer() {
    return new Cesium.Viewer(
        "cesiumContainer",
        {
            animation: false,
            timeline: false
        }
    );
}

Такой подход позволяет централизованно управлять настройками визуализатора.


Модуль core

Папка core содержит фундаментальные механизмы приложения.

Пример:

core/
├── App.js
├── EventBus.js
├── Config.js
└── Constants.js

Здесь размещаются:

  • конфигурации;
  • глобальные события;
  • константы;
  • механизмы запуска приложения.

Пример конфигурации:

export const Config = {
    terrain: true,
    shadows: true,
    fps: false
};

Организация слоев

Большинство проектов работают со множеством слоев данных.

Для них выделяется отдельный каталог.

layers/
├── imagery/
├── terrain/
├── vector/
└── tiles/

Imagery Layers

imagery/
├── BingLayer.js
├── OSMProvider.js
└── ArcGISLayer.js

Пример:

export function addOSMLayer(viewer) {
    viewer.imageryLayers.addImageryProvider(
        new Cesium.OpenStreetMapImageryProvider()
    );
}

Terrain Layers

terrain/
├── WorldTerrain.js
└── LocalTerrain.js

Пример:

export async function loadTerrain() {
    return await Cesium.createWorldTerrainAsync();
}

3D Tiles

tiles/
├── BuildingTiles.js
├── CityTiles.js
└── IndustryTiles.js

Пример:

export async function loadBuildings(viewer) {
    const tileset =
        await Cesium.Cesium3DTileset.fromUrl(
            "/assets/tiles/city/tileset.json"
        );

    viewer.scene.primitives.add(
        tileset
    );
}

Организация сущностей

В проектах на Cesium часто используется API Entity.

Для него удобно создать отдельный раздел.

entities/
├── Aircraft.js
├── Vehicle.js
├── Satellite.js
└── Sensor.js

Каждый файл отвечает за один тип объектов.

Пример:

export function createAircraft(viewer) {
    return viewer.entities.add({
        position:
            Cesium.Cartesian3.fromDegrees(
                37.6176,
                55.7558,
                10000
            ),

        model: {
            uri: "/assets/models/aircraft.glb"
        }
    });
}

Такое разделение предотвращает образование огромных файлов с сотнями объектов.


Организация примитивов

При работе с большими объемами данных часто используются Primitive.

Структура:

primitives/
├── PointCloud.js
├── PolylineCollection.js
├── GroundPrimitive.js
└── CustomPrimitive.js

Пример:

export function createPolyline(
    viewer,
    positions
) {
    const primitive =
        new Cesium.PolylineCollection();

    primitive.add({
        positions
    });

    viewer.scene.primitives.add(
        primitive
    );
}

Каталог сервисов

Сервисы отвечают за работу с внешними источниками данных.

services/
├── ApiService.js
├── GeoJsonService.js
├── TileService.js
└── TrackingService.js

Пример:

export async function loadGeoJson(url) {
    const response = await fetch(url);

    return await response.json();
}

Сервис не должен содержать код отображения данных.

Его задача — только получение и обработка информации.


Организация пользовательских компонентов

В сложных проектах появляются собственные элементы интерфейса.

widgets/
├── LayerPanel.js
├── SearchPanel.js
├── MeasurementTool.js
└── CoordinatesWidget.js

Пример:

export class CoordinatesWidget {

    constructor(viewer) {
        this.viewer = viewer;
    }

    initialize() {
        // логика виджета
    }

}

Подобная организация напоминает архитектуру современных SPA-приложений.


Каталог утилит

Утилиты содержат вспомогательные функции общего назначения.

utils/
├── coordinates.js
├── colors.js
├── geometry.js
└── time.js

Пример:

export function toDegrees(
    longitude,
    latitude
) {
    return Cesium.Cartographic.fromDegrees(
        longitude,
        latitude
    );
}

Утилиты должны быть независимыми от конкретной бизнес-логики.


Организация стилей

Даже если большая часть визуализации создается через WebGL, интерфейс приложения требует CSS.

Структура:

styles/
├── main.css
├── viewer.css
├── panels.css
└── widgets.css

Подключение:

import "./styles/main.css";

Разделение стилей по назначению значительно упрощает сопровождение интерфейса.


Конфигурационные файлы

package.json

Содержит:

  • зависимости;
  • команды запуска;
  • информацию о проекте.

Пример:

{
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  }
}

vite.config.js

При использовании Vite обычно создается отдельная конфигурация.

Пример:

import { defineConfig } from "vite";

export default defineConfig({
    server: {
        port: 3000
    }
});

Архитектурное разделение ответственности

Эффективная структура CesiumJS-проекта обычно строится по следующему принципу:

UI
│
├── Widgets
│
Application
│
├── Services
├── Layers
├── Entities
│
Infrastructure
│
├── Viewer
├── Core
└── Utils

Каждый уровень знает только о нижележащих слоях.

Например:

Widgets
    ↓
Services
    ↓
Viewer

Но не наоборот.

Это уменьшает связанность модулей и облегчает тестирование.


Пример структуры крупного проекта

src/
│
├── core/
│   ├── App.js
│   ├── Config.js
│   └── EventBus.js
│
├── viewer/
│   ├── createViewer.js
│   ├── configureCamera.js
│   └── configureScene.js
│
├── layers/
│   ├── imagery/
│   ├── terrain/
│   └── tiles/
│
├── entities/
│   ├── Aircraft.js
│   ├── Vehicle.js
│   └── Satellite.js
│
├── primitives/
│   ├── PointCloud.js
│   └── CustomPrimitive.js
│
├── widgets/
│   ├── LayerPanel.js
│   ├── MeasurementTool.js
│   └── SearchPanel.js
│
├── services/
│   ├── ApiService.js
│   ├── GeoJsonService.js
│   └── TrackingService.js
│
├── utils/
│   ├── geometry.js
│   ├── coordinates.js
│   └── time.js
│
├── styles/
│   ├── main.css
│   └── widgets.css
│
└── main.js

Подобная организация подходит как для небольших геоинформационных приложений, так и для крупных систем мониторинга, цифровых двойников, навигационных платформ и решений, работающих с 3D Tiles, Terrain и потоковыми пространственными данными в реальном времени.