# eehitus-3d-twin library

The 3D Map of Estonia.
The library is developed to work inside the E-Ehitus application ecosystem and can't be deployed independetly,

---

## Table of Contents

* [Getting started with development](#getting-started-with-development)
    * [Prerequisites](#prerequisites)
    * [Running locally](#running-locally)
    * [Local development](#local-development)
* [Development patterns overview](#development-patterns-overview)
    * [Common components](#common-components)
    * [Form validation](#form-validation)
    * [Translations](#translations)
    * [API requests](#api-requests)
    * [Testing](#testing)
    * [Debugging with source-maps](#debugging-with-source-maps)
    * [Branching](#branching)
* [Old Readme start](#old-readme-start)
    * [Troubleshooting](#4-troubleshooting)

---

## Getting started with development

### Prerequisites

- Node.js (v18.20.6)
- Yarn (v1.x)

* Recommendation:
    * [Node version manager](https://github.com/nvm-sh/nvm)
    * [Node version manager for Windows](https://github.com/coreybutler/nvm-windows)


### Running locally

* `npm i yarn -g` - If you don't have yarn installed locally for given node version
* `yarn install`
* `yarn start`

### Local development

`yarn start` opens `http://localhost:3000`

You have to look for BrowserRouter definition to find the urls you can navigate to.
Usually can be found at `./src/App.tsx` or in the component rendered inside it.

* `./public/config.js` contains all the endpoint URL-s for different microservices.
    * Default endpoint should end with `/v1`
    * You can specify a certain API to point to your projects API `/project_ingress`

---

## Development patterns overview

---

### Common components
* [eehitus-ui-extras](https://git.ehr.ee/ehr/ehr-k8s-pipeline/ui-extras)
    * New common business logic components or enums, should be added to this library
* [TEDI library](https://github.com/TEHIK-EE/tedi-design-system) - E-Ehitus is moving to use Estonian TEDI library for react.
    * New features should be developed using them, if possible.
* [ehr-components](https://git.ehr.ee/ehr/ehr-k8s-pipeline/ehr-components) Old components
    * These are old components and you should not create new functionality with them if possible.
* [eehitus-visuaal](https://git.ehr.ee/ehr/ehr-k8s-pipeline/e-ehitus_visuaal) eehitus-ui has usage of global styles
    * When making changes there, make sure your change does not have impact to the rest of the UI libraries
---

### Form validation

For form validation we use [Yup](https://www.npmjs.com/package/yup).

If you have not used it before look find examples used in eehitus-document-ui.

For an Example find usage of: `useYupValidation`

---

### Translations

All the translations exist in `./src/translations`

Webpack at build time merges all the translations into 3 specific files:
* example_project_name_et.json
* example_project_name_en.json
* example_project_name_ru.json

To add new translation files under `./src/translations`

Follow that the file ends with `*_et.json` pattern

---

### API requests

We use axios and we generate the entire API interface for all service requests using OpenApi Generator.

The API clients are located in `./src/services`

#### Generating API with OpenApi Generator

openapi-generator version = 6.6.0

version 6 is required for axios 0.x
```cmd
$ npm install -g @openapitools/openapi-generator-cli
```
```cmd
$ openapi-generator-cli version-manager set 6
```
If backend is running and openapi doc is accessible (change the port to your matching BE running port)
```cmd
$ openapi-generator-cli generate -i  http://localhost:9000/v3/api-docs -g typescript-axios --additional-properties=modelPropertyNaming=original --type-mappings=DateTime=Date --type-mappings=object=any
```
If you need to update service clients with master yaml
*  Find the yaml in [Gitlab swagger project](https://git.ehr.ee/ehr/ehr-k8s-pipeline/swaggerui/-/tree/master/src/swaggers?ref_type=heads) and download it
*  Create a new folder
*  Open terminal in the same folder with the yaml
```cmd
$ openapi-generator-cli generate -i rename-this-to-your-file.yaml -g typescript-axios --additional-properties=modelPropertyNaming=original --type-mappings=DateTime=Date --type-mappings=object=any
```
*  Copy the .ts files into your projects `./src/services/you-api` folder

---

### Testing

For library unit testing we use Jest.

[E2E Tests](https://git.ehr.ee/ehr/ehr-k8s-pipeline/ehr-e2e) are located in this project.

---

### Debugging with source-maps

If you have developed locally and when you update the library version in eehitus-ui project and start receiving errors,
it's wise to generate local build using `yarn cibuild:debug`

This generates `./dist` folder

Then go into `eehitus-ui` project
* Remove .cache `eehitus-ui/node_modules/.cache`
* Remove old dist `eehitus-ui/node_modules/eehitus-*project_name*/dist`
* Copy your project local debug build dist folder to `eehitus-ui/node_modules/eehitus-*project_name*`
* Start `eehitus-ui` using `yarn start` now the error should have source-maps and debugging it, should be easier.

---

### Branching

For developing we use `Git Flow` branching pattern

Example branch names
* release/project_name
* fix/TICKET_NR-short-description
* feature/TICKET_NR-short-description
* company_name/feature/TICKET_NR-short-description

We don't recommend creating `develop` branch.

Good approach is to create a `release/*` branch and all new development in `company_name/feature/*` you create a Merge Request against `release/*`

You create tags on the `release/*` branch and you have a separate `project namespace` in rancher where you update `eehitus-ui`

---

### Publishing

To get new version of the library up.

1. Go to [Gitlab](https://git.ehr.ee/)
2. Find your project and go to tags page [document-ui as example](https://git.ehr.ee/ehr/ehr-k8s-pipeline/ehr-document-ui/-/tags)
3. Create a New tag, specify version in semver pattern `version-*.*.*` add a description of project tag or other description
4. Update your projects `eehitus-ui/package.json` version and run `yarn` to generate new `yarn.lock` file and commit it
5. Create a New tag for `eehitus-ui`
6. Update the `eehitus-ui` in rancher project_namespace, you should never update the `v1` ingress `eehitus-ui`. `v1` represents master and should always be in stable state.

---

## Old readme start

---

### EHR related
 thus the following libraries must also be present:
- `"ehr-components": "0.2.17"`
- `"react-intl": "5.25.1"`
- `"axios": "0.21.0"`
- `"ehr-auth-react": "0.0.38"`
- `"keycloak-js": "11.0.2"`

### Quickstart
All previous dependencies can be installed (e.g. on a fresh project) using `yarn add react react-dom ehr-components react-intl axios ehr-auth-react keycloak-js`, but check after installation correct versions in package.json file.

## Installation

### 1. Install the ehr-3d-twin dependencie

`yarn add ehr-3d-twin`

### 2. Expose CESIUM_BASE_URL

#### The Easiest way (CDN)
Importing Cesium CDN into the head of the `public/index.html`

    <script src="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Cesium.js"></script>
    <link href="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Widgets/widgets.css" rel="stylesheet">

#### Statically serve the cesium built files with custom bundler
1. Copy cesium static files into  `/public`
   ```
     node_modules/cesium/Build/Cesium/Workers
     node_modules/cesium/Build/Cesium/ThirdParty
     node_modules/cesium/Build/Cesium/Assets
     node_modules/cesium/Build/Cesium/Widgets
     node_modules/cesium/Build/Cesium/Cesium.js
   ```
2. Configure the application bundler to serve these files
3. Set the CESIUM_BASE_URL to point to the serving location (e.g.`window.CESIUM_BASE_URL = '/';`) **This should be the first line of code**
4. Import cesium into the project
    ```
      import * as Cesium from 'cesium';
      import "cesium/Build/Cesium/Widgets/widgets.css";
    ```

#### More information on this method
- [Cesium quickstart](https://cesium.com/docs/tutorials/quick-start/)
- [Cesium and webpack](https://cesium.com/docs/tutorials/cesium-and-webpack/)




**Note:** The mta styles should be also included for the application to look as expected.

on index.js
  ```
    import "../node_modules/ehr-components/build/static/css/mta_visuaal.css";
  ```

**Typescript is fully supported.**

## Props

Required props should be passed on `<Map3D />`.

| Prop | Type | Description | Example |
| ---- | ---- | ----------- | ------- |
| config | `Map3DProps` | Sets the initial state of the component. The urls for the external services are required. | <code>{<br/>base:<br/>{<br/>urls:<br/>{<br/>APP: 'https://livekluster.ehr.ee',<br/>APP_1: 'https://livekluster.ehr.ee',<br/>APP_2: 'https://livekluster.ehr.ee',<br/>APP_3: 'https://livekluster.ehr.ee',<br/>APP_4: 'https://livekluster.ehr.ee',<br/>APP_ENDING: '/',<br/>GEOPORTAL: 'http://localhost:3000/index.php',<br/>MAAMET: 'https://www.maaamet.ee/et',<br/>MAAMET_MAP: 'https://kaart.maaamet.ee',<br/>MAAMET_TILES: 'https://tiles.maaamet.ee',<br/>MAAMET_XGIS: 'https://xgis.maaamet.ee',<br/>MAAMET_KATASTER: 'https://ky.kataster.ee',<br/>KINNISTUSRAAMAT: 'https://kinnistusraamat.rik.ee',<br/>GSAVALIK: 'https://gsavalik.envir.ee',<br/>TALLINN: 'https://gis.tallinn.ee/veebikaart/',<br/>DOCUMENT_SERVICE_ENDPOINT: 'https://livekluster.ehr.ee/api/document/v1',<br/>CLASSIFIER_SERVICE_ENDPOINT: 'https://livekluster.ehr.ee/api/classifier/v1',<br/>PLANK: 'https://planeeringud.ee'<br/>}<br/>}</code>|

Full list of supported initial configurations:
```typescript tsx
  config: {
    layers?: {
      layers2D?: {
        showAdministrationUnits?: {
          showing: boolean,
          transparency: 100,
        },
        showBridges?: {
          showing: boolean,
          transparency: 100,
        },
        showCadastres?: {
          showing: boolean,
          transparency: 100,
        },
        showGeology?: {
          showing: boolean,
          transparency: 100,
        },
        showKPO?: {
          showing: boolean,
          transparency: 100,
        },
        showKPOWater?: {
          showing: boolean,
          transparency: 100,
        },
        showKPOEnvironment?: {
          showing: boolean,
          transparency: 100,
        },
        showKPOTechno?: {
          showing: boolean,
          transparency: 100,
        },
        showKPOOther?: {
          showing: boolean,
          transparency: 100,
        },
        showRoads?: {
          showing: boolean,
          transparency: 100,
        },
        showTraps?: {
          showing: boolean,
          transparency: 100,
        },
        showTrafficRoads?: {
          showing: boolean,
          transparency: 100,
        },
        showNoiseWalls?: {
          showing: boolean,
          transparency: 100,
        },
        showBusStops?: {
          showing: boolean,
          transparency: 100,
        },
        showExits?: {
          showing: boolean,
          transparency: 100,
        },
        showCrossings?: {
          showing: boolean,
          transparency: 100,
        },
        showHydrants?: {
          showing: boolean,
          transparency: 100,
        },
        showWaterSources?: {
          showing: boolean,
          transparency: 100,
        },
        showTileSquares?: {
          showing: boolean,
          transparency: 100,
        },
      },
      layers3D?: {
        showAirzone?: {
          transparency: 0 | 25 | 50 | 75 | 100,
          showing: boolean,
        },
        showVegetation?: {
          transparency: 0 | 25 | 50 | 75 | 100,
          showing: boolean,
        },
        showRoadsETAK?: {
          showing: boolean,
        },
        showTerrain?: boolean,
        showLOD1?:  {
          transparency: 0 | 25 | 50 | 75 | 100,
          showing: boolean,
        },
        showLOD2?:  {
          transparency: 0 | 25 | 50 | 75 | 100,
          showing: boolean,
        },
        showLOD3?:  {
          transparency: 0 | 25 | 50 | 75 | 100,
          showing: boolean,
        },
        showOldTown?:  {
          transparency: 0 | 25 | 50 | 75 | 100,
          showing: boolean,
        },
        showFloodMap1?:  {
          transparency: 0 | 25 | 50 | 75 | 100,
          showing: boolean,
        },
        showFloodMap10?:  {
          transparency: 0 | 25 | 50 | 75 | 100,
          showing: boolean,
        },
        showFloodMap50?:  {
          transparency: 0 | 25 | 50 | 75 | 100,
          showing: boolean,
        },
        showFloodMap100?:  {
          transparency: 0 | 25 | 50 | 75 | 100,
          showing: boolean,
        },
        showFloodMap1000?:  {
          transparency: 0 | 25 | 50 | 75 | 100,
          showing: boolean,
        },
        showRiskAreas?:  {
          transparency: 0 | 25 | 50 | 75 | 100,
          showing: boolean,
        },
        showTrees?: boolean,
        showForests?: boolean,
        showUtilities?:  {
          transparency: 0 | 25 | 50 | 75 | 100,
          showing: boolean,
        },
        showColorHeights?: boolean,
        showColorByUsage?: boolean,
        showLabels?: boolean,
        showShadows?: boolean,
        excludedEhrCodes?: string[],
      },
    },
      functionalities?: {
        showDistanceMeasurement?: boolean,
        showMeasurement3D?: boolean,
        showDownloadBox?: boolean,
      },
      base: {
        showOnBoarding?: boolean,
        transparencyCanShow?: boolean,
        urls: {
          APP: string,
          APP_1: string,
          APP_2: string,
          APP_3: string,
          APP_4: string,
          APP_ENDING: string,
          GEOPORTAL: string,
          MAAMET: string,
          MAAMET_MAP: string,
          MAAMET_TILES: string,
          MAAMET_XGIS: string,
          MAAMET_KATASTER: string,
          KINNISTUSRAAMAT: string,
          GSAVALIK: string,
          TALLINN: string,
          PLANK: string,
		  DOCUMENT_SERVICE_ENDPOINT: string,
		  CLASSIFIER_SERVICE_ENDPOINT: string
        },
      },
      customBuildings?: {
        userFiles?: {
          files: EditableCustomFile[],
          filesProcessing: ToggleableAlert[],
          filesFailed: ToggleableAlert[],
          filesSucceeded: ToggleableAlert[],
        },
        sharedFiles?: CustomFile[],
        sharedCodes?: string[],
      },
      ui?: {
        layersPanelOpen?: boolean,
        basePanelOpen?: boolean,
        isLoading?: boolean,
        depthTest?: boolean,
      },
  }

  interface CustomFile {
    code: string,
    name: string,
    isVisible: boolean,
    urls: string[],
    ehrCode: string[],
  }

  interface EditableCustomFile extends CustomFile {
    isDeleting: boolean,
  }

  interface ToggleableAlert {
    code: string,
    errorCode: number,
    name: string,
    isVisible: boolean,
  }
```

*Note:* The value of these props should not be changed during runtime.

Props can be also passed (optionally) to the `<Controls />` elements to set initial behaviour.

| Prop | Type | Description | Example |
| ---- | ---- | ----------- | ------- |
| coords | <code>Cesium.Cartesian3 &#124; undefined</code> | The starting coordinates. | `coords={new Cartesian3(3049660.4432725203, 1395285.2320070753, 5407747.336997399)}` |
| orientation | <code>Cesium.HeadingPitchRoll &#124; undefined</code> | The starting orientation of the camera. | `orientation={{ heading: 3.8891689628741344, pitch: -0.5010550170855081, roll: 6.280859574464753 }}` |
| ehrCode | <code>string &#124; undefined</code> | The application will **request** for the bounding box of the ehr code from the search api and use this as the starting position. | `ehrCode='120836089'` |
| cadastreCode | <code>string &#124; undefined</code> | The application will **request** for the bounding box of the cadastre from the search api and use this as the starting position. | `cadastreCode='78407:701:0267'` |
| config | <code>ControlsConfig &#124; undefined</code> | Configuration object for showing the UI buttons, by default everything is shown. | <code>config={{<br/>timeline: {<br/>visible: false,<br/>},<br/>layers: {<br/>visible: true,<br/>layers3D: {<br/>visible: true,<br/>AirzoneLayerButton: {<br/>visible: false,<br/>},<br/>},<br/>customBuildings: {<br/>visible: true,<br/>userBuildings: {<br/>visible: false,<br/>}<br/>}<br/>}<br/>}}</code> |

Full list of controls config object
````typescript
  {
  attribution?: {
    visible?: boolean,
  },
  baseLayerButton?: {
    visible?: boolean,
  },
  coordinatesInspector?: {
    visible?: boolean,
  },
  distanceMeasurementButton?: {
    visible?: boolean,
  },
  downloadButton?: {
    visible?: boolean,
  },
  fullscreenButton?: {
    visible?: boolean,
  },
  infoButton?: {
    visible?: boolean,
  },
  locationButton?: {
    visible?: boolean,
  },
  measurement3DButton?: {
    visible?: boolean,
  },
  search?: {
    visible?: boolean,
  },
  terrainTransparencyButton?: {
    visible?: boolean,
  },
  timeline?: {
    visible?: boolean,
  },
  zoomButtons?: {
    visible?: boolean,
  },
  layers?: {
    visible?: boolean,

      buildings?: {
      visible?: boolean,

      lod1Buildings?: boolean,
      lod2Buildings?: boolean,
      lod3Buildings?: boolean,
      oldTown?: boolean,
      utilities?: boolean,
      shadows?: boolean,
    },

    environment?: {
      visible?: boolean,

      vegetation?: boolean,
      cadastre?: boolean,
    },

    water?: {
      visible?: boolean,

      waterSources?: boolean,
      hydrants?: boolean,
    },

    roads?: {
      visible?: boolean,

      roads?: boolean,
      lightRoads?: boolean,
      busStops?: boolean,
      crossings?: boolean,
      exits?: boolean,
      bridges?: boolean,
      trups?: boolean,
      noiseWalls?: boolean,
    },

    areasAndRestrictions?: {
      visible?: boolean,

      airZones?: boolean,
      kpoWater?: boolean,
      kpoEnvironment?: boolean,
      kpoTechno?: boolean,
      kpoOther?: boolean,
      riskAreas?: boolean,
      floodMaps?: boolean,
    },

    information?: {
      visible?: boolean,

      geology?: boolean,
      tileSquares?: boolean,
      labels?: boolean,
    },

    customBuildings?: {
      visible?: boolean,

      sharedBuildings?: {
      visible?: boolean,
      },
      userBuildings?: {
        visible?: boolean,
      }
    },

    modellingTool?: {
      visible?: boolean,
    },

    thirdPartyLayers?: {
      visible?: boolean,
    }
  }
}
````


## 4. Troubleshooting

### Text is not showing correctly (e.g. onBoarding.mouseLeft)
The library uses React Intl to configure the showing text.
Inside the messages json that is used on `IntlProvider` put the corresponding keys with the translations.
A json with all the keys (+ estonian translations) can be found in `node_modules/ehr-3d-twin/etc/sample-intl.json`

### Invalid hook call. Hooks can only be called inside of the body of a function component.

Check the `React` and `ReactDOM` versions of `ehr-components` (e.g. `npm ls react`, `npm ls react-dom`).
Make sure you are using those exact versions in your application (in the package.json)
Add a resolution field (for all other libraries that use `React` and `ReactDOM` to follow)
  ```
    "resolutions": {
    "react": "18.2.0",
      "react-dom": "18.2.0"
    }
  ```

### Error: Cannot find module 'eslint-visitor-keys

Add a resolution field

  ```
    "resolutions": {
		"eslint": "7.28.0"
    },
  ```

### Uncaught Error: [React Intl] Could not find required `intl` object. <IntlProvider> needs to exist in the component ancestry.

Check the `ReactIntl` version of `ehr-component (e.g. `npm ls react-intl`)
Make sure you are using that exact version in your application (in the package.json)
Add a resolution field

  ```
    "resolutions": {
      "react": "18.2.0",
      "react-dom": "18.2.0",
      "react-intl": "5.25.1"
    },
  ```


### index.js:1 Error constructing CesiumWidget. Visit <a href="http://get.webgl.org">http://get.webgl.org</a> to verify that your web browser and hardware support WebGL.

The Cesium library is not imported correctly *(see 2. Expose CESIUM_BASE_URL)*


### Application is not showing correctly / styles are missing

Mta styles are not included in the package, you can include them either on the index.html, on the index.js|ts(x), or on a sass file.

e.g. on a sass file
  ```
    @import "~ehr-components/build/static/css/mta_visuaal.css"
  ```

on index.js
  ```
    import "../node_modules/ehr-components/build/static/css/mta_visuaal.css";

  ```

### Application is not responding

Make sure you are not using React.StrictMode
