Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ui/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
"test": {
"builder": "@angular/build:unit-test",
"options": {
"runnerConfig": "vitest.config.ts",
"coverage": true,
"coverageReporters": ["html", "lcovonly"]
}
Expand Down Expand Up @@ -295,6 +296,7 @@
"test": {
"builder": "@angular/build:unit-test",
"options": {
"runnerConfig": "vitest.config.ts",
"coverage": true,
"coverageReporters": ["html", "lcovonly"]
}
Expand Down Expand Up @@ -422,6 +424,7 @@
"test": {
"builder": "@angular/build:unit-test",
"options": {
"runnerConfig": "vitest.config.ts",
"coverage": true,
"coverageReporters": ["html", "lcovonly"]
}
Expand Down Expand Up @@ -549,6 +552,7 @@
"test": {
"builder": "@angular/build:unit-test",
"options": {
"runnerConfig": "vitest.config.ts",
"coverage": true,
"coverageReporters": ["html", "lcovonly"]
}
Expand Down Expand Up @@ -676,6 +680,7 @@
"test": {
"builder": "@angular/build:unit-test",
"options": {
"runnerConfig": "vitest.config.ts",
"coverage": true,
"coverageReporters": ["html", "lcovonly"]
}
Expand Down Expand Up @@ -806,6 +811,7 @@
"test": {
"builder": "@angular/build:unit-test",
"options": {
"runnerConfig": "vitest.config.ts",
"coverage": true,
"coverageReporters": ["html", "lcovonly"]
}
Expand Down Expand Up @@ -919,6 +925,7 @@
"test": {
"builder": "@angular/build:unit-test",
"options": {
"runnerConfig": "vitest.config.ts",
"coverage": true,
"coverageReporters": ["html", "lcovonly"]
}
Expand Down Expand Up @@ -966,6 +973,7 @@
"test": {
"builder": "@angular/build:unit-test",
"options": {
"runnerConfig": "vitest.config.ts",
"coverage": true,
"coverageReporters": ["html", "lcovonly"]
}
Expand Down Expand Up @@ -1085,6 +1093,7 @@
"test": {
"builder": "@angular/build:unit-test",
"options": {
"runnerConfig": "vitest.config.ts",
"coverage": true,
"coverageReporters": ["html", "lcovonly"]
}
Expand Down Expand Up @@ -1204,6 +1213,7 @@
"test": {
"builder": "@angular/build:unit-test",
"options": {
"runnerConfig": "vitest.config.ts",
"coverage": true,
"coverageReporters": ["html", "lcovonly"]
}
Expand Down
1 change: 0 additions & 1 deletion ui/empty-module.js

This file was deleted.

4 changes: 3 additions & 1 deletion ui/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ build project="gis": common::_clear
run project="gis" nightly="false": common::_clear
npm run serve:{{ project }}:{{ if nightly == "true" { "nightly" } else { "local" } }}

[arg("app", long="app", help="Whether to only run tests for a specific app, e.g. `--app=gis`.")]
[arg("include", long="include", help="Whether to include only specific test files, e.g. `--include=`.")]
[arg("ci", long="ci", value="true", help="Whether to run tests in CI mode.")]
test ci="false" include="" filter="": common::_clear
test ci="false" app="" include="" filter="": common::_clear
npm run test{{ if ci == "true" { ":browser:ci" } else { "" } }} \
-- \
{{ app }} \
{{ if include == "" { "" } else { "--include=" + include } }} \
{{ if filter == "" { "" } else { '--filter="' + filter + '"' } }}

Expand Down
7 changes: 4 additions & 3 deletions ui/projects/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,14 @@ export * from './lib/util/guards/can-register.guard';
export * from './lib/util/guards/log-in.guard';

// Misc
export * from './lib/colors/colormaps/mpl-colormaps';
export * from './lib/colors/colormaps/colormaps';
export * from './lib/colors/colormaps/mpl-colormaps';
export * from './lib/util/assertions';
export * from './lib/util/constants';
export * from './lib/util/conversions';
export * from './lib/util/directives/flexbox-legacy.directive';
export * from './lib/util/directives/autocomplete-select.directive';
export * from './lib/util/directives/flexbox-legacy.directive';
export * from './lib/util/errors';
export * from './lib/util/form.validators';
export * from './lib/util/symbologies';
export * from './lib/util/icons';
export * from './lib/util/symbologies';
15 changes: 15 additions & 0 deletions ui/projects/common/src/lib/util/assertions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {assertNever, isNullOrUndefined} from './assertions';

describe('assertions', () => {
it('should handle never assertions', () => {
expect(() => assertNever(undefined as never)).toThrow();
});

it('should handle null or undefined assertions', () => {
expect(isNullOrUndefined(null)).toBe(true);
expect(isNullOrUndefined(undefined)).toBe(true);
expect(isNullOrUndefined(0)).toBe(false);
expect(isNullOrUndefined('')).toBe(false);
expect(isNullOrUndefined(false)).toBe(false);
});
});
9 changes: 9 additions & 0 deletions ui/projects/common/src/lib/util/assertions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* A helper function that provides both compile-time type checking and runtime error throwing if invalid data bypasses TypeScript (e.g., untyped API responses).
* @param x The value that should never occur
*/
export function assertNever(x: never): never {
throw new Error(`Unexpected value: ${JSON.stringify(x)}`);
}

export const isNullOrUndefined = <T>(value: T | null | undefined): boolean => value === null || value === undefined;
33 changes: 33 additions & 0 deletions ui/projects/core/src/lib/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,26 @@ export interface ConfigDefaults {
readonly FOCUS_EXTENT: [number, number, number, number];
}

export interface DrawSettings {
readonly DRAW_STYLE: DrawSettingsStyle;
readonly AFTER_DRAW_STYLE: DrawSettingsStyle;
}

export interface DrawSettingsStyle {
readonly STROKE_COLOR: string;
readonly STROKE_CONTRAST_COLOR: string;
readonly FILL_COLOR: string;
readonly WIDTH: number;
readonly IMAGE_WIDTH: number;
readonly DASH_PATTERN: number[] | undefined;
}

export interface ConfigMap {
readonly BASEMAPS: Basemaps;
readonly DEFAULT_BASEMAP: keyof Basemaps;
readonly REFRESH_LAYERS_ON_CHANGE: boolean;
readonly VALID_CRS: Array<string>;
readonly DRAWING: DrawSettings;
}

export type Basemaps = Record<string, Basemap>;
Expand Down Expand Up @@ -149,6 +164,24 @@ export const DEFAULT_CORE_CONFIG: CoreConfigStructure = {
},
REFRESH_LAYERS_ON_CHANGE: false,
VALID_CRS: ['EPSG:4326', 'EPSG:3857'],
DRAWING: {
DRAW_STYLE: {
FILL_COLOR: 'rgba(25, 118, 210, 0.3)',
STROKE_COLOR: '#1976d2',
STROKE_CONTRAST_COLOR: '#FFFFFF',
WIDTH: 4,
IMAGE_WIDTH: 5,
DASH_PATTERN: [8, 8],
},
AFTER_DRAW_STYLE: {
FILL_COLOR: 'rgba(25, 118, 210, 0.3)',
STROKE_COLOR: '#1976d2',
STROKE_CONTRAST_COLOR: '#FFFFFF',
WIDTH: 4,
IMAGE_WIDTH: 5,
DASH_PATTERN: [8, 8],
},
},
},
API_URL: '/api',
TIME: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
input,
viewChild,
viewChildren,
signal,
} from '@angular/core';

import OlMap from 'ol/Map';
Expand All @@ -42,6 +43,7 @@ import {ol as flatgeobuf} from 'flatgeobuf';
import OlStyleFill from 'ol/style/Fill';
import OlStyleStroke from 'ol/style/Stroke';
import OlStyleStyle, {StyleLike as OlStyleLike} from 'ol/style/Style';
import OlCircleStyle from 'ol/style/Circle';

import OlInteractionDraw, {GeometryFunction} from 'ol/interaction/Draw';
import OlInteractionSelect from 'ol/interaction/Select';
Expand All @@ -53,7 +55,7 @@ import {MapLayerComponent} from '../map-layer.component';

import {FeatureSelection, ProjectService} from '../../project/project.service';
import {Extent, MapService} from '../map.service';
import {Basemap, CoreConfig, VectorTiles, Wms} from '../../config.service';
import {Basemap, CoreConfig, DrawSettingsStyle, VectorTiles, Wms} from '../../config.service';
import {MatGridList, MatGridListModule, MatGridTile} from '@angular/material/grid-list';
import {SpatialReferenceService, WGS_84} from '../../spatial-references/spatial-reference.service';
import {containsCoordinate, getCenter} from 'ol/extent';
Expand Down Expand Up @@ -115,6 +117,8 @@ export class MapContainerComponent implements AfterViewInit, OnChanges, OnDestro
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private backgroundLayers: Array<OlLayer<OlSource, any>> = [];

readonly overlayLayer = signal<OlLayerVector<OlSourceVector<OlFeature>> | undefined>(undefined);

private userSelect?: OlInteractionSelect;

private selectedFeature?: OlFeature<OlGeometry> = undefined;
Expand Down Expand Up @@ -157,6 +161,14 @@ export class MapContainerComponent implements AfterViewInit, OnChanges, OnDestro
this.redrawLayers(projection);
});
});

effect(() => {
this.overlayLayer();
this.projection$.pipe(first()).subscribe((projection) => {
this.backgroundLayerSource = undefined; // reset source to force recreation
this.redrawLayers(projection);
});
});
}

ngOnDestroy(): void {
Expand Down Expand Up @@ -293,17 +305,26 @@ export class MapContainerComponent implements AfterViewInit, OnChanges, OnDestro
this.projection$.pipe(first()).subscribe((projection) => this.redrawLayers(projection));
}

public getMap(index = 0): OlMap {
return this.maps[index];
}

private createDrawInteractionLayer(): OlLayerVector<OlSourceVector<OlFeature>> {
return new OlLayerVector({
const layer = new OlLayerVector({
source: this.drawInteractionSource,
style: [drawContrastStrokeStyle(this.config.MAP.DRAWING.AFTER_DRAW_STYLE), drawStyle(this.config.MAP.DRAWING.AFTER_DRAW_STYLE)],
});

return layer;
}

private createDrawInteraction(): OlInteractionDraw {
return new OlInteractionDraw({
source: this.drawInteractionSource,
type: this.drawType,
geometryFunction: this.drawGeometryFunction,
maxPoints: this.drawSingleFeature ? 2 : undefined,
style: [drawContrastStrokeStyle(this.config.MAP.DRAWING.DRAW_STYLE), drawStyle(this.config.MAP.DRAWING.DRAW_STYLE)],
});
}

Expand Down Expand Up @@ -527,6 +548,11 @@ export class MapContainerComponent implements AfterViewInit, OnChanges, OnDestro
} else {
this.mapLayers.forEach((layerComponent) => map.addLayer(layerComponent.mapLayer));
}

const overlayLayer = this.overlayLayer();
if (overlayLayer) {
map.getLayers().push(overlayLayer);
}
});

this.reattachDrawInteractions();
Expand Down Expand Up @@ -808,3 +834,43 @@ export class MapContainerComponent implements AfterViewInit, OnChanges, OnDestro
return mapCanvas.toDataURL('image/png');
}
}

/**
* Returns an OpenLayers style for drawing features based on the provided draw settings.
*/
export function drawStyle(style: DrawSettingsStyle): OlStyleStyle {
const {STROKE_COLOR, WIDTH, DASH_PATTERN, FILL_COLOR, IMAGE_WIDTH} = style;
return new OlStyleStyle({
fill: new OlStyleFill({
color: FILL_COLOR,
}),
stroke: new OlStyleStroke({
color: STROKE_COLOR,
width: WIDTH + 2,
lineDash: DASH_PATTERN,
}),
image: new OlCircleStyle({
radius: IMAGE_WIDTH,
fill: new OlStyleFill({
color: FILL_COLOR,
}),
stroke: new OlStyleStroke({
color: STROKE_COLOR,
}),
}),
});
}

/**
* Returns an OpenLayers style for drawing features with a contrasting stroke based on the provided draw settings.
*/
export function drawContrastStrokeStyle(style: DrawSettingsStyle): OlStyleStyle {
const {WIDTH, DASH_PATTERN, STROKE_CONTRAST_COLOR: STROKE_CONSTRAST_COLOR} = style;
return new OlStyleStyle({
stroke: new OlStyleStroke({
color: STROKE_CONSTRAST_COLOR,
width: WIDTH + 2,
lineDash: DASH_PATTERN,
}),
});
}
13 changes: 9 additions & 4 deletions ui/projects/core/src/lib/map/map.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import {BehaviorSubject, Observable} from 'rxjs';
import {Injectable} from '@angular/core';
import {containsExtent as olExtentContainsExtent, getIntersection as olExtentGetIntersection} from 'ol/extent';
import OlGeometry from 'ol/geom/Geometry';
import {Vector as OlSourceVector} from 'ol/source';
import {Type as OlGeometryType} from 'ol/geom/Geometry';
import {Vector as OlSourceVector} from 'ol/source';
import OlView from 'ol/View';
import OlFeature from 'ol/Feature';

import {MapContainerComponent} from './map-container/map-container.component';
import {createBox} from 'ol/interaction/Draw';
import {createBox, GeometryFunction} from 'ol/interaction/Draw';
import {olExtentToTuple} from '@geoengine/common';

/**
Expand Down Expand Up @@ -86,11 +86,16 @@ export class MapService {
this.mapComponent = mapComponent;
}

public startDrawInteraction(drawType: OlGeometryType): void {
public startDrawInteraction(
drawType: OlGeometryType,
drawSingleFeature = false,
geometryFunction?: GeometryFunction,
endDrawCallback?: (feature: OlFeature<OlGeometry>) => void,
): void {
if (!this.mapComponent) {
throw new Error('no MapComponent registered');
}
this.mapComponent.startDrawInteraction(drawType);
this.mapComponent.startDrawInteraction(drawType, drawSingleFeature, geometryFunction, endDrawCallback);
}

public startBoxDrawInteraction(endDrawCallback?: (feature: OlFeature<OlGeometry>) => void): void {
Expand Down
Loading
Loading