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
292 changes: 265 additions & 27 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@
"@ngx-translate/core": "^16.0.4",
"@ngx-translate/http-loader": "^16.0.1",
"@openid/appauth": "^1.3.1",
"@placemarkio/check-geojson": "^0.1.14",
"@sentry/browser": "^7.37.2",
"@tmcw/togeojson": "^7.1.2",
"@turf/clean-coords": "^7.2.0",
"@turf/truncate": "^7.2.0",
"@turf/turf": "^7.1.0",
"@types/leaflet-draw": "^1.0.6",
"angular-svg-icon": "^16.1.0",
Expand All @@ -106,6 +110,7 @@
"ngx-file-drop": "^16.0.0",
"ngx-logger": "^4.2.1",
"ngx-markdown": "^20.0.0",
"nve-designsystem": "^2.16.0",
"observable-webworker": "^3.4.0",
"pouchdb-adapter-idb": "^7.2.2",
"rxjs": "^7.8.1",
Expand Down
1 change: 1 addition & 0 deletions src/app/core/services/database/database.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export class DatabaseService {
* @returns Returns a promise with the value of the given key
*/
async get<T>(key: string): Promise<T> {
// TODO: Legg til null på type
await firstValueFrom(this.ready$);
return this.database.get(key);
}
Expand Down
8 changes: 8 additions & 0 deletions src/app/core/services/geojson/geojson-item.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface GeoJSONItem {
id: string;
name: string;
date?: number;
visibleOnMap?: boolean;
comment?: string;
lengthKm?: number;
}
127 changes: 127 additions & 0 deletions src/app/core/services/geojson/geojson.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Injectable, effect, inject, signal } from '@angular/core';
import { DatabaseService } from '../database/database.service';
import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
import { GeoJSONItem } from './geojson-item.model';
import { LoggingService } from 'src/app/modules/shared/services/logging/logging.service';
import { toObservable } from '@angular/core/rxjs-interop';
import { cleanFeatureCollection } from 'src/app/pages/plans/geojson';
import { length } from '@turf/turf';

const DEBUG_TAG = 'GeoJSON';

@Injectable({
providedIn: 'root',
})
export class GeoJSONService {
private db = inject(DatabaseService);
private logger = inject(LoggingService);

metadata = signal<GeoJSONItem[]>([]);
readonly metadata$ = toObservable(this.metadata);
private initialized = false;

constructor() {
this.init();

effect(() => {
const metadata = this.metadata();
if (!this.initialized) return;
this.saveMetadata(metadata);
});
}

private async init() {
this.logger.debug('Init', DEBUG_TAG);
const items = await this.getMetadata();
if (items && items.length > 0) {
this.metadata.set(items);
}
setTimeout(() => (this.initialized = true)); // For å unngå en første unødvendig lagring i effecten
}

private async saveMetadata(items: GeoJSONItem[]) {
this.logger.debug('Saving metadata', DEBUG_TAG, { n: items.length });
await this.db.set('geojson-metadata', items);
}

private getMetadata() {
this.logger.debug('Reading metadata', DEBUG_TAG);
return this.db.get<GeoJSONItem[]>('geojson-metadata');
}

/**
* Kalkulerer lengden av LineString features i et GeoJSON objekt
* @param geojson
* @returns lengde i kilometer
*/
private calculateLengthKm(geojson: Feature<Geometry, GeoJsonProperties>[]): number {
return geojson.reduce((sum, feature) => sum + length(feature), 0);
}

/**
* Updates metadata for a given item
* @param item the geojson item to update
*/
updateMetadata(item: GeoJSONItem) {
this.metadata.update((items) => {
const other = items.filter((x) => x.id !== item.id);
return [...other, item];
});
}

/**
* Save a geojson object with a given id
* @param metadata metadata for the geojson
* @param geojson geojson object
*/
async save(metadata: GeoJSONItem, geojson: FeatureCollection): Promise<void> {
this.logger.debug('Save', DEBUG_TAG, { metadata });
try {
cleanFeatureCollection(geojson);
} catch (error) {
this.logger.error(error, DEBUG_TAG, 'Error in cleaning process, but object may be mutated - half cleaned');
}

try {
const lineFeatures = geojson.features.filter((f) => f.geometry.type === 'LineString');

if (lineFeatures.length) {
const lengthKm = this.calculateLengthKm(lineFeatures);
metadata.lengthKm = lengthKm;
}

await this.db.set(`geojson:${metadata.id}`, geojson);
this.metadata.update((items) => [...items, metadata]);
} catch (error) {
this.logger.error(error, DEBUG_TAG, 'Could not save', { metadata, geojson });
throw error;
}
}

/**
* Get a geojson object by id
* @param id unique id for the geojson
*/
async get(id: GeoJSONItem['id']): Promise<FeatureCollection> {
this.logger.debug('Get', DEBUG_TAG, { id });
return this.db.get<FeatureCollection>(`geojson:${id}`);
}

/**
* Remove a geojson object by id
* @param id unique id for the geojson
*/
async remove(id: GeoJSONItem['id']): Promise<void> {
this.logger.debug('Remove', DEBUG_TAG, { id });
await this.db.remove(`geojson:${id}`);
this.metadata.update((items) => items.filter((item) => item.id !== id));
}

/**
* List all geojson ids
*/
// async listIds(): Promise<GeoJSONItem['id'][]> {
// const keys = await this.db.keys();
// return keys.filter((k) => k.startsWith('geojson:')).map((k) => k.replace('geojson:', ''));
// }
}
1 change: 1 addition & 0 deletions src/app/modules/map-image/map-image.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class MapImageComponent {
...x.layerConfig.options,
})
);

const mapSettings: L.MapOptions = {
zoom: settings.map.tiles.zoomLevelObservationList,
maxZoom: settings.map.tiles.maxZoom,
Expand Down
14 changes: 5 additions & 9 deletions src/app/modules/map/components/map/map.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,14 @@
></app-map-controls>
}

@if (showObserverTrips()) {
<div
#observerTripsContainer
class="observer-trip-backdrop"
style="display: none"
(click)="removeObserverTripDescription()"
>
<!-- TODO: I stedet for dette, få opp detaljside for en tur som modal? -->
@if (showObserverTrips() && metadataName()) {
<div class="observer-trip-backdrop" (click)="removeObserverTripDescription()">
<div class="observer-trip-desc">
<div>
<strong>Obsturbeskrivelse{{ observationTripName ? " " + observationTripName : "" }}:</strong>
<strong>Obsturbeskrivelse{{ metadataName() ? " " + metadataName() : "" }}:</strong>
</div>
<div>{{ observationTripDescription }}</div>
<div>{{ metadataDescription() }}</div>
</div>
</div>
}
Loading