From c4196579b65c7854e7818a6e2b6a27435e749a34 Mon Sep 17 00:00:00 2001 From: Vijeinath Tissaveerasingham Date: Thu, 25 Jan 2024 10:39:40 +0100 Subject: [PATCH] add map component --- angular.json | 1 + package-lock.json | 77 +++++++++++++++ package.json | 3 + src/app/app.module.ts | 4 +- src/app/app.routing.ts | 2 +- src/app/map/map.component.html | 4 + src/app/map/map.component.scss | 10 ++ src/app/map/map.component.spec.ts | 23 +++++ src/app/map/map.component.ts | 93 +++++++++++++++++++ .../resource/location/location.component.scss | 4 + .../manuscript-entry.component.html | 4 + .../manuscript-entry.component.ts | 15 ++- .../only-transcription.component.ts | 6 +- src/app/services/beol.service.ts | 49 +++++++++- src/assets/images/blue_marker.svg | 7 ++ src/assets/images/red_marker.svg | 22 +++++ src/environments/environment.prod.ts | 5 +- src/environments/environment.ts | 5 +- 18 files changed, 322 insertions(+), 12 deletions(-) create mode 100644 src/app/map/map.component.html create mode 100644 src/app/map/map.component.scss create mode 100644 src/app/map/map.component.spec.ts create mode 100644 src/app/map/map.component.ts create mode 100644 src/assets/images/blue_marker.svg create mode 100644 src/assets/images/red_marker.svg diff --git a/angular.json b/angular.json index 1fe081e4..bb0e7e5b 100644 --- a/angular.json +++ b/angular.json @@ -28,6 +28,7 @@ ], "styles": [ "src/styles.scss", + "node_modules/leaflet/dist/leaflet.css", "src/assets/style/main.scss", "src/assets/style/material-theme.scss" ], diff --git a/package-lock.json b/package-lock.json index 944b259c..0de3b2af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,8 @@ "core-js": "^3.6.5", "jdnconvertiblecalendar": "^0.0.7", "jdnconvertiblecalendardateadapter": "^1.0.1", + "leaflet": "^1.9.4", + "leaflet-arrowheads": "^1.4.0", "ngx-color-picker": "^13.0.0", "openseadragon": "^2.4.2", "rxjs": "~6.5.5", @@ -46,6 +48,7 @@ "@angular/language-service": "^15.2.10", "@types/jasmine": "~3.6.0", "@types/jasminewd2": "~2.0.3", + "@types/leaflet": "^1.9.8", "@types/node": "^12.11.1", "@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/parser": "^5.43.0", @@ -4798,6 +4801,12 @@ "@types/send": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.13", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", + "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==", + "dev": true + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -4839,6 +4848,15 @@ "resolved": "https://registry.npmjs.org/@types/jsonld/-/jsonld-1.5.13.tgz", "integrity": "sha512-n7fUU6W4kSYK8VQlf/LsE9kddBHPKhODoVOjsZswmve+2qLwBy6naWxs/EiuSZN9NU0N06Ra01FR+j87C62T0A==" }, + "node_modules/@types/leaflet": { + "version": "1.9.8", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.8.tgz", + "integrity": "sha512-EXdsL4EhoUtGm2GC2ZYtXn+Fzc6pluVgagvo2VC1RHWToLGlTRwVYoDpqS/7QXa01rmDyBjJk3Catpf60VMkwg==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -11160,6 +11178,28 @@ } } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" + }, + "node_modules/leaflet-arrowheads": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/leaflet-arrowheads/-/leaflet-arrowheads-1.4.0.tgz", + "integrity": "sha512-aIjsmoWe1VJXaGOpKpS6E8EzN2vpx3GGCNP/FxQteLVzAg5xMID7elf9hj/1CWLJo8FuGRjSvKkUQDj7mocrYA==", + "dependencies": { + "leaflet": "^1.7.1", + "leaflet-geometryutil": "^0.10.0" + } + }, + "node_modules/leaflet-geometryutil": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/leaflet-geometryutil/-/leaflet-geometryutil-0.10.3.tgz", + "integrity": "sha512-Qeas+KsnenE0Km/ydt8km3AqFe7kJhVwuLdbCYM2xe2epsxv5UFEaVJiagvP9fnxS8QvBNbm7DJlDA0tkKo9VA==", + "dependencies": { + "leaflet": "^1.6.0" + } + }, "node_modules/less": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", @@ -20367,6 +20407,12 @@ "@types/send": "*" } }, + "@types/geojson": { + "version": "7946.0.13", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", + "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==", + "dev": true + }, "@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -20408,6 +20454,15 @@ "resolved": "https://registry.npmjs.org/@types/jsonld/-/jsonld-1.5.13.tgz", "integrity": "sha512-n7fUU6W4kSYK8VQlf/LsE9kddBHPKhODoVOjsZswmve+2qLwBy6naWxs/EiuSZN9NU0N06Ra01FR+j87C62T0A==" }, + "@types/leaflet": { + "version": "1.9.8", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.8.tgz", + "integrity": "sha512-EXdsL4EhoUtGm2GC2ZYtXn+Fzc6pluVgagvo2VC1RHWToLGlTRwVYoDpqS/7QXa01rmDyBjJk3Catpf60VMkwg==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, "@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -25155,6 +25210,28 @@ "node-fetch": "3.0.0-beta.9" } }, + "leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" + }, + "leaflet-arrowheads": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/leaflet-arrowheads/-/leaflet-arrowheads-1.4.0.tgz", + "integrity": "sha512-aIjsmoWe1VJXaGOpKpS6E8EzN2vpx3GGCNP/FxQteLVzAg5xMID7elf9hj/1CWLJo8FuGRjSvKkUQDj7mocrYA==", + "requires": { + "leaflet": "^1.7.1", + "leaflet-geometryutil": "^0.10.0" + } + }, + "leaflet-geometryutil": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/leaflet-geometryutil/-/leaflet-geometryutil-0.10.3.tgz", + "integrity": "sha512-Qeas+KsnenE0Km/ydt8km3AqFe7kJhVwuLdbCYM2xe2epsxv5UFEaVJiagvP9fnxS8QvBNbm7DJlDA0tkKo9VA==", + "requires": { + "leaflet": "^1.6.0" + } + }, "less": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", diff --git a/package.json b/package.json index b4c42f55..8cd53683 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,8 @@ "core-js": "^3.6.5", "jdnconvertiblecalendar": "^0.0.7", "jdnconvertiblecalendardateadapter": "^1.0.1", + "leaflet": "^1.9.4", + "leaflet-arrowheads": "^1.4.0", "ngx-color-picker": "^13.0.0", "openseadragon": "^2.4.2", "rxjs": "~6.5.5", @@ -51,6 +53,7 @@ "@angular/language-service": "^15.2.10", "@types/jasmine": "~3.6.0", "@types/jasminewd2": "~2.0.3", + "@types/leaflet": "^1.9.8", "@types/node": "^12.11.1", "@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/parser": "^5.43.0", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4b0446c7..d0596433 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -69,6 +69,7 @@ import { ArkUrlDialogComponent } from './dialog/ark-url-dialog.component'; import { ReisbuechleinUriPipe } from './pipes/reisbuechlein-uri.pipe'; import { OnlyTranscriptionComponent } from './resource/page-transcription/only-transcription/only-transcription.component'; import { LocationComponent } from './resource/location/location.component'; +import { MapComponent } from './map/map.component'; @NgModule({ declarations: [ @@ -112,7 +113,8 @@ import { LocationComponent } from './resource/location/location.component'; ArkUrlDialogComponent, ReisbuechleinUriPipe, OnlyTranscriptionComponent, - LocationComponent + LocationComponent, + MapComponent ], imports: [ AppRouting, diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index 4187744a..18dc71fc 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -26,7 +26,7 @@ import { CommentComponent } from './resource/comment/comment.component'; import { BiographyComponent } from './biography/biography.component'; import { LeceLeooComponent } from './lece-leoo/lece-leoo.component'; import { PageTranscriptionComponent } from './resource/page-transcription/page-transcription.component'; -import {LocationComponent} from './resource/location/location.component'; +import { LocationComponent } from './resource/location/location.component'; const appRoutes: Routes = [ diff --git a/src/app/map/map.component.html b/src/app/map/map.component.html new file mode 100644 index 00000000..f71358a1 --- /dev/null +++ b/src/app/map/map.component.html @@ -0,0 +1,4 @@ +
+
+
+ diff --git a/src/app/map/map.component.scss b/src/app/map/map.component.scss new file mode 100644 index 00000000..cad97e40 --- /dev/null +++ b/src/app/map/map.component.scss @@ -0,0 +1,10 @@ +.map-frame { + border: 2px solid black; + height: 100%; + margin: 2rem 0; +} + +#map { + height: 700px; +} + diff --git a/src/app/map/map.component.spec.ts b/src/app/map/map.component.spec.ts new file mode 100644 index 00000000..2a47736c --- /dev/null +++ b/src/app/map/map.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MapComponent } from './map.component'; + +describe('MapComponent', () => { + let component: MapComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ MapComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(MapComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/map/map.component.ts b/src/app/map/map.component.ts new file mode 100644 index 00000000..5823221d --- /dev/null +++ b/src/app/map/map.component.ts @@ -0,0 +1,93 @@ +import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { DataGraphDB } from '../services/beol.service'; +import * as L from 'leaflet'; +import 'leaflet-arrowheads'; +import { environment } from '../../environments/environment'; + +@Component({ + selector: 'app-map', + templateUrl: './map.component.html', + styleUrls: ['./map.component.scss'] +}) +export class MapComponent implements OnChanges { + @Input() mapData: DataGraphDB; + map: any; + constructor() { + } + + ngOnChanges(changes: SimpleChanges): void { + this.initMap(); + } + + private initMap() { + const origin = this.get_origin() + const destination = this.get_destination() + const centroid = this.calculate_center(origin, destination) + this.loadMap(centroid); + this.addMarkers(origin, destination); + this.addLines(); + } + + private get_origin(): L.LatLng { + const latitude = this.mapData.results.bindings[0].startLat.value + const longitude = this.mapData.results.bindings[0].startLong.value + return new L.LatLng(latitude, longitude) + } + + private get_destination(): L.LatLng { + const length = this.mapData.results.bindings.length + const lastObject = this.mapData.results.bindings[length-1] + const latitude = lastObject.endLat.value + const longitude = lastObject.endLong.value + return new L.LatLng(latitude, longitude) + } + private calculate_center(origin: L.LatLng, destination: L.LatLng): L.LatLng { + const middle_point_lat = (origin.lat + destination.lat)/2 + const middle_point_lng = (origin.lng + destination.lng)/2 + return new L.LatLng(middle_point_lat, middle_point_lng) + } + + private loadMap(centroid: L.LatLngExpression): void { + this.map = L.map('map', {center: centroid, zoom: 8.4}); + const tiles = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', { + attribution: 'Map data © OpenStreetMap contributors, Imagery © Mapbox', + maxZoom: 18, + id: 'mapbox/streets-v11', + tileSize: 512, + zoomOffset: -1, + accessToken: environment.mapbox.accessToken, + }) + tiles.addTo(this.map); + } + + private addMarkers(origin_coord: L.LatLng, destination_coord: L.LatLng): void { + const origin = L.marker(origin_coord, { + icon: new L.Icon({ + iconSize: new L.Point(40, 40), + iconAnchor: [13, 41], + iconUrl: 'assets/images/red_marker.svg', + }), title: 'Workspace' + } as L.MarkerOptions); + origin.addTo(this.map); + + const destination = L.marker(destination_coord, { + icon: new L.Icon({ + iconSize: new L.Point(40, 40), + iconAnchor: [13, 41], + iconUrl: 'assets/images/blue_marker.svg', + }), title: 'Workspace' + } as L.MarkerOptions); + destination.addTo(this.map); + } + + private addLines(): void { + this.mapData.results.bindings.forEach((obj, index) => { + const start = new L.LatLng(obj.startLat.value, obj.startLong.value) + const end = new L.LatLng(obj.endLat.value, obj.endLong.value) + const line = L.polyline([start, end], { + color: '#800080' + } as L.PolylineOptions).arrowheads({size: '15%'}); + line.addTo(this.map) + }) + } +} diff --git a/src/app/resource/location/location.component.scss b/src/app/resource/location/location.component.scss index 1ace052e..4a5f315c 100644 --- a/src/app/resource/location/location.component.scss +++ b/src/app/resource/location/location.component.scss @@ -25,6 +25,10 @@ margin-inline-end: 14rem; max-width: 1000px; align-content: center; + + h1 { + text-align: left; + } } } diff --git a/src/app/resource/manuscript-entry/manuscript-entry.component.html b/src/app/resource/manuscript-entry/manuscript-entry.component.html index 2c28cf1e..08c16684 100644 --- a/src/app/resource/manuscript-entry/manuscript-entry.component.html +++ b/src/app/resource/manuscript-entry/manuscript-entry.component.html @@ -118,6 +118,10 @@

Stages

+ + + + diff --git a/src/app/resource/manuscript-entry/manuscript-entry.component.ts b/src/app/resource/manuscript-entry/manuscript-entry.component.ts index aee5eb59..c136a465 100644 --- a/src/app/resource/manuscript-entry/manuscript-entry.component.ts +++ b/src/app/resource/manuscript-entry/manuscript-entry.component.ts @@ -22,7 +22,6 @@ import { MatDialog } from '@angular/material/dialog'; import { map } from 'rxjs/operators'; class ManuscriptEntryProps implements PropertyValues { - title: ReadTextValue[] = []; seqnum: ReadIntValue[] = []; page: ReadLinkValue[] = []; @@ -55,9 +54,10 @@ export class ManuscriptEntryComponent extends BeolResource { }; props: ManuscriptEntryProps; transcriptions: ReadResource[] = []; + pages$: Observable; journey$: Observable; stages$: Observable; - pages$: Observable; + mapCoordinates$: Observable; constructor( @Inject(DspApiConnectionToken) protected _dspApiConnection: KnoraApiConnection, @@ -88,6 +88,7 @@ export class ManuscriptEntryComponent extends BeolResource { this.getPages(); this.getJourney(); this.getStages(); + this.getMapCoordinates(); } } @@ -118,6 +119,12 @@ export class ManuscriptEntryComponent extends BeolResource { this.pages$ = this._dspApiConnection.v2.search.doExtendedSearch(gravsearch); } + /** + * Function that adds the uri information to the data. + * + * @param data + * @private + */ private addURI(data: DataGraphDB) { const headerWithIRI = data.head.vars.filter((item: string) => item.endsWith("Iri")); data.head.vars = data.head.vars.filter((item: string) => !item.endsWith("Iri")); @@ -151,6 +158,10 @@ export class ManuscriptEntryComponent extends BeolResource { ); } + private getMapCoordinates() { + this.mapCoordinates$ = this._beolService.make_coordinates_query(this.iri); + } + goToLocation(resIri) { this.goToResource(this._appInitService.config['ontologyIRI'] + '/ontology/0801/beol/v2#Location', resIri, undefined); } diff --git a/src/app/resource/page-transcription/only-transcription/only-transcription.component.ts b/src/app/resource/page-transcription/only-transcription/only-transcription.component.ts index 117d406e..e7975274 100644 --- a/src/app/resource/page-transcription/only-transcription/only-transcription.component.ts +++ b/src/app/resource/page-transcription/only-transcription/only-transcription.component.ts @@ -32,7 +32,7 @@ class TranscriptionProps implements PropertyValues { templateUrl: './only-transcription.component.html', styleUrls: ['./only-transcription.component.scss'] }) -export class OnlyTranscriptionComponent implements OnInit, OnChanges { +export class OnlyTranscriptionComponent implements OnChanges { @Input() iri: string; errorMessage: any; propIris: PropIriToNameMapping = { @@ -57,10 +57,6 @@ export class OnlyTranscriptionComponent implements OnInit, OnChanges { public dialog: MatDialog ) {} - ngOnInit() { - console.log(this.iri); - } - ngOnChanges(changes: SimpleChanges): void { if (this.iri) { this.getResource(this.iri); diff --git a/src/app/services/beol.service.ts b/src/app/services/beol.service.ts index e94cfeab..5b9e6874 100644 --- a/src/app/services/beol.service.ts +++ b/src/app/services/beol.service.ts @@ -737,7 +737,7 @@ export class BeolService { return this.requestGraphDB(journeyTemplate); } - getStages(entryIri: string):Observable { + getStages(entryIri: string): Observable { const stageTemplate = ` PREFIX trip-onto: PREFIX schema: @@ -772,6 +772,53 @@ export class BeolService { return this.requestGraphDB(stageTemplate); } + make_coordinates_query(entryIri: string): Observable { + const stageCoordinatesTemplate = ` + PREFIX trip-onto: + PREFIX schema: + PREFIX owl: + PREFIX wd: + PREFIX wdp: + PREFIX wdps: + PREFIX rdfs: + PREFIX wikibase: + SELECT ?startWikiIri ?startLat ?startLong ?endWikiIri ?endLat ?endLong ?startDate ?endDate ?Transportation + WHERE { + ?journey a trip-onto:Journey . + BIND(<${entryIri}> AS ?entryIRI) + BIND (<< ?person trip-onto:hasJourney ?journey>> AS ?journeyTriple) + ?journeyTriple trip-onto:mentionedIn ?entryIRI . + ?journeyTriple trip-onto:hasStage ?stage . + BIND(<> AS ?stageTriple) + ?stage trip-onto:hasStartLocation ?FromLocation . + ?FromLocation trip-onto:hasWikiLink ?startWikiIri . + + ?stageTriple trip-onto:hasEndDate ?endDate . + ?stageTriple trip-onto:hasStartDate ?startDate . + ?stage trip-onto:hasDestination ?ToLocation . + ?ToLocation trip-onto:hasWikiLink ?endWikiIri . + ?stage trip-onto:meanOfTransportation ?meanOfTransportation . + ?meanOfTransportation schema:name ?Transportation . + OPTIONAL{ + SERVICE { + ?startWikiIri wdp:P625 ?coordinate_start. + + ?coordinate_start wdps:P625 ?coordinate_node_start. + ?coordinate_node_start wikibase:geoLongitude ?startLong. + ?coordinate_node_start wikibase:geoLatitude ?startLat. + + ?endWikiIri wdp:P625 ?coordinate_end . + ?coordinate_end wdps:P625 ?coordinate_node_end. + ?coordinate_node_end wikibase:geoLongitude ?endLong. + ?coordinate_node_end wikibase:geoLatitude ?endLat. + + } + } + } + `; + return this.requestGraphDB(stageCoordinatesTemplate) + } + requestGraphDB(query: string): Observable { const url = "https://graphdb.organa.myds.me/repositories/journeyStar"; const httpOptions = { diff --git a/src/assets/images/blue_marker.svg b/src/assets/images/blue_marker.svg new file mode 100644 index 00000000..3d369a5a --- /dev/null +++ b/src/assets/images/blue_marker.svg @@ -0,0 +1,7 @@ + + + + + + map-marker Created with Sketch. + \ No newline at end of file diff --git a/src/assets/images/red_marker.svg b/src/assets/images/red_marker.svg new file mode 100644 index 00000000..c456cbc2 --- /dev/null +++ b/src/assets/images/red_marker.svg @@ -0,0 +1,22 @@ + + + + map-marker + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 8a3db376..105aff71 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,4 +1,7 @@ export const environment = { production: true, - name: 'prod' + name: 'prod', + mapbox: { + accessToken: 'pk.eyJ1Ijoicm9kcmlnb2thbWFkYSIsImEiOiJjbGZ5NTVhenAwanBzM3Fta3Y3b29temE5In0.PkdHrkHBrx9RALYhyLMRxA', + }, }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 7a45d047..c853bba8 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,4 +1,7 @@ export const environment = { name: 'dev', - production: false + production: false, + mapbox: { + accessToken: 'pk.eyJ1Ijoicm9kcmlnb2thbWFkYSIsImEiOiJjbGZ5NTVhenAwanBzM3Fta3Y3b29temE5In0.PkdHrkHBrx9RALYhyLMRxA', + }, };