Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
Sweetnow committed Dec 3, 2024
0 parents commit e992cad
Show file tree
Hide file tree
Showing 56 changed files with 7,717 additions and 0 deletions.
45 changes: 45 additions & 0 deletions .github/workflows/build-and-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Wails build

on:
push:
tags:
- 'v*.*.*'

env:
# Necessary for most environments as build failure can occur due to OOM issues
NODE_OPTIONS: "--max-old-space-size=4096"

jobs:
build:
strategy:
# Failure in one platform build won't impact the others
fail-fast: false
matrix:
build:
- name: 'moss-ui'
platform: 'linux/amd64'
os: 'ubuntu-latest'
- name: 'moss-ui'
platform: 'windows/amd64'
os: 'windows-latest'
- name: 'moss-ui'
platform: 'darwin/universal'
os: 'macos-latest'

runs-on: ${{ matrix.build.os }}
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: recursive

- name: Build wails
uses: dAppServer/[email protected]
id: build
with:
build-name: ${{ matrix.build.name }}
build-platform: ${{ matrix.build.platform }}
package: true
go-version: '1.23'
node-version: '20.x'
nsis: false
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build/bin
node_modules
frontend/dist
data/
log/
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# MOSS-UI

A simple UI to visualize the AVRO output of the MOSS project.

The project is **unstable** now and faces data reading performance challenges to handle the AVRO output data without indexing that is usually provided by a database.

ATTENTION:
- The car model in the project is 5m × 2m, which cannot be automatically adjusted by the vehicle attributes in the moss input person file.

## How to run

1. Get the output dir of the MOSS project.
```python
from moss import Engine, TlPolicy, Verbosity
MAP_PATH = "./data/temp/map.pb"
TRIP_PATH = "./data/temp/persons.pb"
eng = Engine(
+ name="test",
map_file=MAP_PATH,
person_file=TRIP_PATH,
start_step=8 * 3600,
+ output_dir="./output",
verbose_level=Verbosity.ALL,
device=0,
)
```
The related documentation is https://docs.fiblab.net/moss#Engine.

2. Open the MOSS-UI and select the output directory. In the file dialog, please select the `moss.yml` file in the output directory of the MOSS project.

![MOSS-UI](./docs/1.png)

3. When you open a file, please wait for the data to be loaded. If the loading process is finished, you will see a message like below and the map will move to the center of the output data.

![MOSS-UI](./docs/2.png)

4. You can open/close the layer by clicking the icon on the top. You can also change the time by dragging the slider on the bottom.

## For Developers

This is a Wails React-TS project.

You can configure the project by editing `wails.json`. More information about the project settings can be found
here: https://wails.io/docs/reference/project-config

#### Prequisites

You will need to have the Wails development environment installed. You can find instructions here: https://wails.io/docs/gettingstarted/installation

#### Live Development

To run in live development mode, run `wails dev` in the project directory. This will run a Vite development
server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser
and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect
to this in your browser, and you can call your Go code from devtools.

#### Building

To build a redistributable, production mode package, use `wails build`.

Command to build the project for all platforms:
```bash
wails build -platform windows/amd64
```
215 changes: 215 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package main

import (
"context"
"encoding/base64"
"errors"
"fmt"
"math"
"os"
"path"
"path/filepath"
"sort"

"github.com/samber/lo"
"github.com/wailsapp/wails/v2/pkg/runtime"
)

const TITLE = "MObility Simulation System UI"

type Sim struct {
Name string `json:"name"`
Start float64 `json:"start"`
Steps float64 `json:"steps"`
MapBase64 string `json:"map_base64"`
}

// App struct
type App struct {
ctx context.Context

tlLoader *Loader[FrontendTL, *FrontendTL]
vehicleLoader *Loader[FrontendCar, *FrontendCar]
pedestrianLoader *Loader[FrontendPedestrian, *FrontendPedestrian]
}

// NewApp creates a new App application struct
func NewApp() *App {
return &App{}
}

// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}

type LoadReturn struct {
Sim *Sim `json:"sim"`
Err string `json:"err"`
}

// try to load a directory with MOSS output files
func (a *App) Open() LoadReturn {
file, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
Title: "Select a MOSS output project",
DefaultFilename: "moss.yml",
})
if err != nil {
return LoadReturn{nil, err.Error()}
}
absFile, err := filepath.Abs(file)
if err != nil {
return LoadReturn{nil, err.Error()}
}
dir := filepath.Dir(absFile)

// 1. load dir / "map.pb"
mapPath := path.Join(dir, "map.pb")
mapBytes, err := os.ReadFile(mapPath)
if err != nil {
// if map.pb not found, return error
if errors.Is(err, os.ErrNotExist) {
return LoadReturn{nil, "`map.pb` not found in the directory"}
}
return LoadReturn{nil, err.Error()}
}
// 2. load dir / "vehicle" / {time}.avro
vehicleDir := path.Join(dir, "vehicle")
vehicleScanner, err := NewScanner(vehicleDir)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return LoadReturn{nil, "`vehicle` directory not found in the directory"}
}
return LoadReturn{nil, err.Error()}
}
a.vehicleLoader = NewLoader(vehicleScanner, NewFrontendCar)

// 3. load dir / "pedestrian" / {time}.avro
pedestrianDir := path.Join(dir, "pedestrian")
pedestrianScanner, err := NewScanner(pedestrianDir)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return LoadReturn{nil, "`pedestrian` directory not found in the directory"}
}
return LoadReturn{nil, err.Error()}
}
a.pedestrianLoader = NewLoader(pedestrianScanner, NewFrontendPedestrian)

// 4. load dir / "tl" / {time}.avro
tlDir := path.Join(dir, "tl")
tlScanner, err := NewScanner(tlDir)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return LoadReturn{nil, "`tl` directory not found in the directory"}
}
return LoadReturn{nil, err.Error()}
}
a.tlLoader = NewLoader(tlScanner, NewFrontendTL)

// build Sim

allStartT := math.Inf(1)
allEndT := math.Inf(-1)
for _, scanner := range []*Scanner{vehicleScanner, pedestrianScanner, tlScanner} {
if startT := scanner.StartT(); startT < allStartT {
allStartT = startT
}
if endT := scanner.EndT(); endT > allEndT {
allEndT = endT
}
}

// modify app title
runtime.WindowSetTitle(a.ctx, TITLE+" - "+dir)

return LoadReturn{&Sim{
Name: dir,
Start: allStartT,
Steps: allEndT - allStartT,
MapBase64: base64.StdEncoding.EncodeToString(mapBytes),
}, ""}
}

func (a *App) Close() {
a.vehicleLoader = nil
a.pedestrianLoader = nil
a.tlLoader = nil
runtime.WindowSetTitle(a.ctx, TITLE)
}

type FetchCarReturn struct {
Data []*FrontendCarFrame `json:"data"`
Err string `json:"err"`
}

func (a *App) FetchCar(startT, endT float64, xMin, xMax, yMin, yMax float32) *FetchCarReturn {
frames, err := a.vehicleLoader.Load(startT, endT)
if err != nil {
return &FetchCarReturn{nil, err.Error()}
}
frontendFrames := make([]*FrontendCarFrame, len(frames))
// filter by x, y
for i, frame := range frames {
frontendFrames[i] = &FrontendCarFrame{
T: frame.T,
Data: lo.Filter(frame.Data, func(car *FrontendCar, _ int) bool {
return car.X >= xMin && car.X <= xMax && car.Y >= yMin && car.Y <= yMax
}),
}
}
// sort by t
sort.Slice(frontendFrames, func(i, j int) bool {
return frontendFrames[i].T < frontendFrames[j].T
})
// debug string
debugStr := fmt.Sprintf("len(FetchCar): %v", len(frontendFrames))
for _, frame := range frontendFrames {
debugStr += fmt.Sprintf("\n t=%v, len(Data): %v", frame.T, len(frame.Data))
}
runtime.LogDebug(a.ctx, debugStr)
return &FetchCarReturn{frontendFrames, ""}
}

type FetchPedReturn struct {
Data []*FrontendPedestrianFrame `json:"data"`
Err string `json:"err"`
}

func (a *App) FetchPed(startT, endT float64, xMin, xMax, yMin, yMax float32) *FetchPedReturn {
frames, err := a.pedestrianLoader.Load(startT, endT)
if err != nil {
return &FetchPedReturn{nil, err.Error()}
}
frontendFrames := make([]*FrontendPedestrianFrame, len(frames))
// filter by x, y
for i, frame := range frames {
frontendFrames[i] = &FrontendPedestrianFrame{
T: frame.T,
Data: lo.Filter(frame.Data, func(ped *FrontendPedestrian, _ int) bool {
return ped.X >= xMin && ped.X <= xMax && ped.Y >= yMin && ped.Y <= yMax
}),
}
}
return &FetchPedReturn{frontendFrames, ""}
}

type FetchTLReturn struct {
Data []*FrontendTLFrame `json:"data"`
Err string `json:"err"`
}

func (a *App) FetchTL(startT, endT float64) *FetchTLReturn {
frames, err := a.tlLoader.Load(startT, endT)
if err != nil {
return &FetchTLReturn{nil, err.Error()}
}
frontendFrames := make([]*FrontendTLFrame, len(frames))
for i, frame := range frames {
frontendFrames[i] = &FrontendTLFrame{
T: frame.T,
Data: frame.Data,
}
}
return &FetchTLReturn{frontendFrames, ""}
}
35 changes: 35 additions & 0 deletions build/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Build Directory

The build directory is used to house all the build files and assets for your application.

The structure is:

* bin - Output directory
* darwin - macOS specific files
* windows - Windows specific files

## Mac

The `darwin` directory holds files specific to Mac builds.
These may be customised and used as part of the build. To return these files to the default state, simply delete them
and
build with `wails build`.

The directory contains the following files:

- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`.
- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`.

## Windows

The `windows` directory contains the manifest and rc files used when building with `wails build`.
These may be customised for your application. To return these files to the default state, simply delete them and
build with `wails build`.

- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to
use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file
will be created using the `appicon.png` file in the build directory.
- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`.
- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer,
as well as the application itself (right click the exe -> properties -> details)
- `wails.exe.manifest` - The main application manifest file.
Binary file added build/appicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit e992cad

Please sign in to comment.