1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-01-02 03:37:54 +02:00

migrating maps to ngx-leaflet #256

This commit is contained in:
Patrik J. Braun 2021-04-25 19:07:02 +02:00
parent f0f47f96bb
commit 4f2b02b7e0
16 changed files with 314 additions and 286 deletions

View File

@ -59,13 +59,19 @@
"tsConfig": "src/frontend/tsconfig.app.json",
"polyfills": "src/frontend/polyfills.ts",
"assets": [
"src/frontend/assets"
"src/frontend/assets",
{
"glob": "**/*",
"input": "node_modules/leaflet/dist/images/",
"output": "./"
}
],
"styles": [
"bootstrap/dist/css/bootstrap.min.css",
"ngx-bootstrap/datepicker/bs-datepicker.css",
"open-iconic/font/css/open-iconic-bootstrap.css",
"ngx-toastr/toastr.css",
"leaflet/dist/leaflet.css",
"src/frontend/styles.css"
],
"scripts": [],
@ -73,11 +79,8 @@
},
"configurations": {
"dev": {
"localize": [
"en",
"hu"
],
"outputPath": "dist",
"outputPath": "dist/en",
"localize": false,
"watch": true
},
"production": {
@ -140,10 +143,16 @@
"bootstrap/dist/css/bootstrap.css",
"open-iconic/font/css/open-iconic-bootstrap.css",
"ngx-bootstrap/datepicker/bs-datepicker.css",
"leaflet/dist/leaflet.css",
"src/frontend/styles.css"
],
"assets": [
"src/frontend/assets"
"src/frontend/assets",
{
"glob": "**/*",
"input": "node_modules/leaflet/dist/images/",
"output": "./"
}
]
}
},

29
package-lock.json generated
View File

@ -764,6 +764,12 @@
}
}
},
"@asymmetrik/ngx-leaflet": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@asymmetrik/ngx-leaflet/-/ngx-leaflet-8.1.0.tgz",
"integrity": "sha512-lq7LduBP/vXcaSEmKnx7mzCR8WsoYqh9pB6BNnq53yeCwsqRbG3GdKye1/i8VvoRzjDsmQBPQsIFZ9uclXrtgg==",
"dev": true
},
"@babel/code-frame": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
@ -3500,9 +3506,9 @@
"dev": true
},
"@types/leaflet": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.2.14.tgz",
"integrity": "sha512-acP2w5DygY0V7bwmjFmaen5I2iBl8RkWx9kon1IJA7k9mNFgBb6702WApjZSrM4AG1ucJVxFcTlS6nr4HvahEw==",
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.7.0.tgz",
"integrity": "sha512-ltv5jR+VjKSMtoDkxH61Rsbo0zLU7iqyOXpVPkAX4F+79fg2eymC7t0msWsfNaEZO1FGTIQATCCCQe+ijWoicg==",
"dev": true,
"requires": {
"@types/geojson": "*"
@ -3868,17 +3874,6 @@
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"dev": true
},
"@yaga/leaflet-ng2": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@yaga/leaflet-ng2/-/leaflet-ng2-1.0.0.tgz",
"integrity": "sha512-8qVI9xophJbMfDeCbHfW91+ksI6snolFV25jw7sg1bpPUVNReWjLGbZEIgshyToppcwKRx0OxHQMb9JvK3bt7w==",
"dev": true,
"requires": {
"@types/geojson": "^7946.0.4",
"@types/leaflet": "^1.2.8",
"leaflet": "^1.3.2"
}
},
"@yarnpkg/lockfile": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
@ -12101,9 +12096,9 @@
}
},
"leaflet": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.3.4.tgz",
"integrity": "sha512-FYL1LGFdj6v+2Ifpw+AcFIuIOqjNggfoLUwuwQv6+3sS21Za7Wvapq+LhbSE4NDXrEj6eYnW3y7LsaBICpyXtw==",
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz",
"integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==",
"dev": true
},
"less": {

View File

@ -74,6 +74,7 @@
"@angular/platform-browser": "11.2.9",
"@angular/platform-browser-dynamic": "11.2.9",
"@angular/router": "11.2.9",
"@asymmetrik/ngx-leaflet": "^8.1.0",
"@ngx-loading-bar/core": "5.1.1",
"@types/bcrypt": "3.0.1",
"@types/bcryptjs": "2.4.2",
@ -90,12 +91,12 @@
"@types/image-size": "0.8.0",
"@types/jasmine": "3.6.9",
"@types/jsonwebtoken": "8.5.1",
"@types/leaflet": "^1.7.0",
"@types/node": "14.14.37",
"@types/node-geocoder": "3.24.1",
"@types/sharp": "0.23.1",
"@types/winston": "2.4.4",
"@types/xml2js": "0.4.8",
"@yaga/leaflet-ng2": "1.0.0",
"bootstrap": "4.6.0",
"chai": "4.3.4",
"chai-http": "4.3.0",
@ -121,6 +122,7 @@
"karma-jasmine-html-reporter": "1.5.4",
"karma-remap-istanbul": "0.6.0",
"karma-systemjs": "0.16.0",
"leaflet": "^1.7.1",
"mocha": "8.3.2",
"natural-orderby": "2.0.3",
"ngx-bootstrap": "6.2.0",

View File

@ -10,7 +10,7 @@ import {FullScreenService} from './ui/gallery/fullscreen.service';
import {AuthenticationService} from './model/network/authentication.service';
import {UserMangerSettingsComponent} from './ui/settings/usermanager/usermanager.settings.component';
import {FrameComponent} from './ui/frame/frame.component';
import {YagaModule} from '@yaga/leaflet-ng2';
import { LeafletModule } from '@asymmetrik/ngx-leaflet';
import {LoadingBarModule} from '@ngx-loading-bar/core';
import {GalleryLightboxMediaComponent} from './ui/gallery/lightbox/media/media.lightbox.gallery.component';
import {GalleryPhotoLoadingComponent} from './ui/gallery/grid/photo/loading/loading.photo.grid.gallery.component';
@ -144,9 +144,9 @@ export class CustomUrlSerializer implements UrlSerializer {
PopoverModule.forRoot(),
BsDropdownModule.forRoot(),
BsDatepickerModule.forRoot(),
YagaModule,
TimepickerModule.forRoot(),
LoadingBarModule
LoadingBarModule,
LeafletModule
],
declarations: [AppComponent,
LoginComponent,

View File

@ -66,6 +66,6 @@
text-decoration: underline;
}
.keywords .oi-person{
.keywords .oi-person {
margin-right: 2px;
}

View File

@ -1,4 +1,4 @@
<div class="content">
<div class="content d-flex flex-column">
<div class="modal-header">
<h2 class="modal-title" i18n>Info</h2>
<button type="button" class="close" (click)="close()" aria-label="Close">
@ -90,19 +90,19 @@
<span class="details-icon oi oi-tags"></span>
</div>
<div class="col-10 keywords">
<ng-template ngFor let-keyword [ngForOf]="keywords" let-last="last">
<a *ngIf="searchEnabled"
[routerLink]="['/search', getTextSearchQuery(keyword.value,keyword.type)]" [ngSwitch]="keyword.type">
<ng-template [ngSwitchCase]="SearchQueryTypes.keyword">#</ng-template><!--
<ng-template ngFor let-keyword [ngForOf]="keywords" let-last="last">
<a *ngIf="searchEnabled"
[routerLink]="['/search', getTextSearchQuery(keyword.value,keyword.type)]" [ngSwitch]="keyword.type">
<ng-template [ngSwitchCase]="SearchQueryTypes.keyword">#</ng-template><!--
-->
<ng-template [ngSwitchCase]="SearchQueryTypes.person"><span class="oi oi-person"></span></ng-template><!--
<ng-template [ngSwitchCase]="SearchQueryTypes.person"><span class="oi oi-person"></span></ng-template><!--
-->{{keyword.value}}</a>
<span *ngIf="!searchEnabled" [ngSwitch]="keyword.type">
<span *ngIf="!searchEnabled" [ngSwitch]="keyword.type">
<ng-template [ngSwitchCase]="SearchQueryTypes.keyword">#</ng-template><!--
--><ng-template [ngSwitchCase]="SearchQueryTypes.person"><span class="oi oi-person"></span></ng-template><!--
-->{{keyword.value}}</span>
<ng-template [ngIf]="!last">,&#32;</ng-template>
</ng-template>
<ng-template [ngIf]="!last">,&#32;</ng-template>
</ng-template>
</div>
</div>
@ -123,20 +123,19 @@
</div>
</div>
</div>
<div class="mt-auto" style="height: 400px">
<div
*ngIf="hasGPS() && mapEnabled"
<div id="map" *ngIf="hasGPS() && mapEnabled">
<yaga-map [zoom]="10"
[lat]="PositionData.GPSData.latitude"
[lng]="PositionData.GPSData.longitude">
<yaga-marker
[lat]="PositionData.GPSData.latitude"
[lng]="PositionData.GPSData.longitude">
</yaga-marker>
<yaga-attribution-control
prefix=""
[attributions]="mapService.Attributions">
</yaga-attribution-control>
<yaga-tile-layer [url]="mapService.MapLayer"></yaga-tile-layer>
</yaga-map>
id="map"
leaflet
[leafletOptions]="{zoom:10,
center:{lat:PositionData.GPSData.latitude,
lng:PositionData.GPSData.longitude},
layers:[baseLayer],
zoomControl: false}"
[leafletLayers]="markerLayer">
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {Component, EventEmitter, Input, OnChanges, OnInit, Output} from '@angular/core';
import {CameraMetadata, PhotoDTO, PhotoMetadata, PositionMetaData} from '../../../../../../common/entities/PhotoDTO';
import {Config} from '../../../../../../common/config/public/Config';
import {MediaDTO, MediaDTOUtils} from '../../../../../../common/entities/MediaDTO';
@ -8,26 +8,31 @@ import {QueryService} from '../../../../model/query.service';
import {MapService} from '../../map/map.service';
import {SearchQueryTypes, TextSearch, TextSearchQueryMatchTypes} from '../../../../../../common/entities/SearchQueryDTO';
import {AuthenticationService} from '../../../../model/network/authentication.service';
import {LatLngLiteral, marker, Marker, TileLayer, tileLayer} from 'leaflet';
@Component({
selector: 'app-info-panel',
styleUrls: ['./info-panel.lightbox.gallery.component.css'],
templateUrl: './info-panel.lightbox.gallery.component.html',
})
export class InfoPanelLightboxComponent implements OnInit {
export class InfoPanelLightboxComponent implements OnInit, OnChanges {
@Input() media: MediaDTO;
@Output() closed = new EventEmitter();
public readonly mapEnabled: boolean;
public readonly searchEnabled: boolean;
keywords: { value: string, type: SearchQueryTypes }[] = null;
readonly SearchQueryTypes: typeof SearchQueryTypes = SearchQueryTypes;
public keywords: { value: string, type: SearchQueryTypes }[] = null;
public readonly SearchQueryTypes: typeof SearchQueryTypes = SearchQueryTypes;
public baseLayer: TileLayer;
public markerLayer: Marker[] = [];
constructor(public queryService: QueryService,
public mapService: MapService,
private authService: AuthenticationService) {
this.mapEnabled = Config.Client.Map.enabled;
this.searchEnabled = Config.Client.Search.enabled && this.authService.canSearch();
this.baseLayer = tileLayer(mapService.MapLayer, {attribution: mapService.ShortAttributions});
}
get FullPath(): string {
@ -53,6 +58,15 @@ export class InfoPanelLightboxComponent implements OnInit {
return (this.media as PhotoDTO).metadata.cameraData;
}
ngOnChanges(): void {
if (this.hasGPS()) {
this.markerLayer = [marker({
lat: this.PositionData.GPSData.latitude,
lng: this.PositionData.GPSData.longitude
} as LatLngLiteral)];
}
}
ngOnInit(): void {
const metadata = this.media.metadata as PhotoMetadata;
if ((metadata.keywords && metadata.keywords.length > 0) ||
@ -101,9 +115,9 @@ export class InfoPanelLightboxComponent implements OnInit {
(this.media as PhotoDTO).metadata.positionData.country);
}
hasGPS(): number {
return (this.media as PhotoDTO).metadata.positionData && (this.media as PhotoDTO).metadata.positionData.GPSData &&
(this.media as PhotoDTO).metadata.positionData.GPSData.latitude && (this.media as PhotoDTO).metadata.positionData.GPSData.longitude;
hasGPS(): boolean {
return !!((this.media as PhotoDTO).metadata.positionData && (this.media as PhotoDTO).metadata.positionData.GPSData &&
(this.media as PhotoDTO).metadata.positionData.GPSData.latitude && (this.media as PhotoDTO).metadata.positionData.GPSData.longitude);
}
getPositionText(): string {

View File

@ -73,13 +73,13 @@
opacity: 1.0;
}
.preview-loading {
::ng-deep .lightbox-map-gallery-component-preview-loading {
background-color: #bbbbbb;
color: #7f7f7f;
font-size: 50px;
}
.preview-loading span {
::ng-deep .lightbox-map-gallery-component-preview-loading span {
top: calc(50% - 25px);
left: calc(50% - 25px);
}

View File

@ -6,75 +6,15 @@
[style.top.px]="lightboxDimension.top"
[style.left.px]="lightboxDimension.left"
[style.opacity]="opacity">
<div
[style.width.px]="mapDimension.width"
[style.height.px]="mapDimension.height"
<yaga-map #yagaMap
[style.width.px]="mapDimension.width"
[style.height.px]="mapDimension.height">
<yaga-layers-control
position="bottomright">
<yaga-feature-group
[caption]="'path'"
*ngIf="paths.length>0"
yaga-overlay-layer="'path'">
<yaga-polyline
*ngFor="let path of paths"
[latLngs]="path">
</yaga-polyline>
<yaga-marker
*ngFor="let path of paths"
[lat]="path[0].lat"
[lng]="path[1].lng">
</yaga-marker>
</yaga-feature-group>
<yaga-feature-group
[caption]="'photos'"
yaga-overlay-layer="'photos'">
<yaga-marker [title]="photo.name"
*ngFor="let photo of mapPhotos"
[lat]="photo.lat"
[lng]="photo.lng">
<yaga-icon
*ngIf="photo.iconUrl"
[iconUrl]="photo.iconUrl"
[iconSize]="yagaMap.zoom < 15 ? smallIconSize : iconSize"
></yaga-icon>
<yaga-popup
(open)="loadPreview(photo)"
[minWidth]="photo.preview.width">
<img *ngIf="photo.preview.thumbnail.Src"
[style.width.px]="photo.preview.width"
[style.height.px]="photo.preview.height"
[src]="photo.preview.thumbnail.Src">
<div class="preview-loading"
*ngIf="!photo.preview.thumbnail.Src"
[style.width.px]="photo.preview.width"
[style.height.px]="photo.preview.height">
<span class="oi"
[ngClass]="photo.preview.thumbnail.Error ? 'oi-warning' : 'oi-picture'"
aria-hidden="true">
</span>
</div>
</yaga-popup>
</yaga-marker>
</yaga-feature-group>
<ng-container *ngFor="let l of mapService.Layers">
<yaga-tile-layer yaga-base-layer
[caption]="l.name"
[url]="l.url"></yaga-tile-layer>
</ng-container>
</yaga-layers-control>
<yaga-zoom-control position="bottomright">
</yaga-zoom-control>
<yaga-attribution-control
prefix=""
[attributions]="mapService.Attributions">
</yaga-attribution-control>
</yaga-map>
leaflet
[leafletOptions]="mapOptions"
(leafletMapZoom)="onLeafletZoom()"
(leafletMapReady)="onMapReady($event)">
</div>
</div>

View File

@ -1,8 +1,8 @@
import {AfterViewInit, Component, ElementRef, HostListener, Input, OnChanges, ViewChild} from '@angular/core';
import {Component, ElementRef, HostListener, Input, OnChanges, ViewChild} from '@angular/core';
import {PhotoDTO} from '../../../../../../common/entities/PhotoDTO';
import {Dimension} from '../../../../model/IRenderable';
import {FullScreenService} from '../../fullscreen.service';
import {IconThumbnail, Thumbnail, ThumbnailManagerService} from '../../thumbnailManager.service';
import {IconThumbnail, Thumbnail, ThumbnailBase, ThumbnailManagerService} from '../../thumbnailManager.service';
import {MediaIcon} from '../../MediaIcon';
import {Media} from '../../Media';
import {PageHelper} from '../../../../model/page.helper';
@ -10,15 +10,31 @@ import {FileDTO} from '../../../../../../common/entities/FileDTO';
import {Utils} from '../../../../../../common/Utils';
import {Config} from '../../../../../../common/config/public/Config';
import {MapService} from '../map.service';
import {LatLng, Point} from 'leaflet';
import {MapComponent} from '@yaga/leaflet-ng2';
import {
control,
Control,
icon,
LatLng,
latLng,
latLngBounds,
layerGroup,
LayerGroup,
Map,
MapOptions,
Marker,
marker,
Point,
polyline,
tileLayer
} from 'leaflet';
import {LeafletControlLayersConfig} from '@asymmetrik/ngx-leaflet';
@Component({
selector: 'app-gallery-map-lightbox',
styleUrls: ['./lightbox.map.gallery.component.css'],
templateUrl: './lightbox.map.gallery.component.html',
})
export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
export class GalleryMapLightboxComponent implements OnChanges {
@Input() photos: PhotoDTO[];
@ -28,19 +44,46 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
public visible = false;
public controllersVisible = false;
public opacity = 1.0;
mapPhotos: MapPhoto[] = [];
paths: LatLng[][] = [];
@ViewChild('root', {static: true}) elementRef: ElementRef;
@ViewChild('yagaMap', {static: true}) yagaMap: MapComponent;
public smallIconSize = new Point(Config.Client.Media.Thumbnail.iconSize * 0.75, Config.Client.Media.Thumbnail.iconSize * 0.75);
public iconSize = new Point(Config.Client.Media.Thumbnail.iconSize, Config.Client.Media.Thumbnail.iconSize);
public mapOptions: MapOptions = {
zoom: 2,
center: latLng(0, 0)
};
private smallIconSize = new Point(Config.Client.Media.Thumbnail.iconSize * 0.75, Config.Client.Media.Thumbnail.iconSize * 0.75);
private iconSize = new Point(Config.Client.Media.Thumbnail.iconSize, Config.Client.Media.Thumbnail.iconSize);
private mapLayersControlOption: LeafletControlLayersConfig & { overlays: { Photos: LayerGroup, Paths: LayerGroup } } =
{baseLayers: {}, overlays: {Photos: layerGroup([]), Paths: layerGroup([])}};
private mapLayerControl: Control.Layers;
private thumbnailsOnLoad: ThumbnailBase[] = [];
private startPosition: Dimension = null;
private leafletMap: Map;
constructor(public fullScreenService: FullScreenService,
private thumbnailService: ThumbnailManagerService,
public mapService: MapService) {
this.mapOptions.layers = [this.mapLayersControlOption.overlays.Photos,
this.mapLayersControlOption.overlays.Paths];
for (let i = 0; i < mapService.Layers.length; ++i) {
const l = mapService.Layers[i];
const tl = tileLayer(l.url, {attribution: mapService.Attributions});
if (i === 0) {
this.mapOptions.layers.push(tl);
}
this.mapLayersControlOption.baseLayers[l.name] = tl;
}
this.mapLayerControl = control.layers(this.mapLayersControlOption.baseLayers,
this.mapLayersControlOption.overlays, {position: 'bottomright'});
}
private static getScreenWidth(): number {
return window.innerWidth;
}
private static getScreenHeight(): number {
return window.innerHeight;
}
ngOnChanges(): void {
if (this.visible === false) {
@ -50,37 +93,24 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
}
ngAfterViewInit(): void {
// TODO: remove it once yaga/leaflet-ng2 is fixes.
// See issue: https://github.com/yagajs/leaflet-ng2/issues/440
let i = 0;
this.yagaMap.eachLayer((l): void => {
if (i >= 3 || (this.paths.length === 0 && i >= 2)) {
this.yagaMap.removeLayer(l);
}
++i;
});
}
@HostListener('window:resize', ['$event'])
async onResize(): Promise<void> {
this.lightboxDimension = ({
top: 0,
left: 0,
width: this.getScreenWidth(),
height: this.getScreenHeight()
width: GalleryMapLightboxComponent.getScreenWidth(),
height: GalleryMapLightboxComponent.getScreenHeight()
} as Dimension);
this.mapDimension = ({
top: 0,
left: 0,
width: this.getScreenWidth(),
height: this.getScreenHeight()
width: GalleryMapLightboxComponent.getScreenWidth(),
height: GalleryMapLightboxComponent.getScreenHeight()
} as Dimension);
await Utils.wait(0);
this.yagaMap.invalidateSize();
this.leafletMap.invalidateSize();
}
public async show(position: Dimension): Promise<void> {
this.hideImages();
this.visible = true;
@ -91,8 +121,8 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
this.mapDimension = ({
top: 0,
left: 0,
width: this.getScreenWidth(),
height: this.getScreenHeight()
width: GalleryMapLightboxComponent.getScreenWidth(),
height: GalleryMapLightboxComponent.getScreenHeight()
} as Dimension);
this.showImages();
this.centerMap();
@ -101,11 +131,11 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
this.lightboxDimension = ({
top: 0,
left: 0,
width: this.getScreenWidth(),
height: this.getScreenHeight()
width: GalleryMapLightboxComponent.getScreenWidth(),
height: GalleryMapLightboxComponent.getScreenHeight()
} as Dimension);
await Utils.wait(350);
this.yagaMap.invalidateSize();
this.leafletMap.invalidateSize();
this.centerMap();
this.controllersVisible = true;
}
@ -116,7 +146,7 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
const to = this.startPosition;
// iff target image out of screen -> scroll to there
if (PageHelper.ScrollY > to.top || PageHelper.ScrollY + this.getScreenHeight() < to.top) {
if (PageHelper.ScrollY > to.top || PageHelper.ScrollY + GalleryMapLightboxComponent.getScreenHeight() < to.top) {
PageHelper.ScrollY = to.top;
}
@ -127,18 +157,30 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
setTimeout((): void => {
this.visible = false;
this.hideImages();
this.yagaMap.zoom = 2;
this.leafletMap.setZoom(2);
}, 500);
}
showImages(): void {
this.hideImages();
this.mapPhotos = this.photos.filter((p): number => {
this.mapLayersControlOption.overlays.Photos.clearLayers();
// make sure to enable photos layers when opening map
if (this.leafletMap && !this.leafletMap.hasLayer(this.mapLayersControlOption.overlays.Photos)) {
this.leafletMap.addLayer(this.mapLayersControlOption.overlays.Photos);
}
this.thumbnailsOnLoad = [];
this.photos.filter((p): number => {
return p.metadata && p.metadata.positionData && p.metadata.positionData.GPSData
&& p.metadata.positionData.GPSData.latitude
&& p.metadata.positionData.GPSData.longitude;
}).map((p): MapPhoto => {
}).forEach((p): void => {
const mkr = marker({
lat: p.metadata.positionData.GPSData.latitude,
lng: p.metadata.positionData.GPSData.longitude
});
this.mapLayersControlOption.overlays.Photos.addLayer(mkr);
let width = 500;
let height = 500;
const size = p.metadata.size;
@ -147,30 +189,56 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
} else {
width = height * (size.width / size.height);
}
const iconTh = this.thumbnailService.getIcon(new MediaIcon(p));
iconTh.Visible = true;
const obj: MapPhoto = {
name: p.name,
lat: p.metadata.positionData.GPSData.latitude,
lng: p.metadata.positionData.GPSData.longitude,
iconThumbnail: iconTh,
preview: {
width,
height,
thumbnail: this.thumbnailService.getLazyThumbnail(new Media(p, width, height))
}
const photoTh = this.thumbnailService.getLazyThumbnail(new Media(p, width, height));
this.thumbnailsOnLoad.push(photoTh);
};
if (Config.Client.Map.useImageMarkers === true) {
if (iconTh.Available === true) {
obj.iconUrl = iconTh.Src;
// Setting popup photo
const setPopUpPhoto = () => {
const photoPopup = `<img style="width: ${width}px; height: ${height}px" ` +
`src="${photoTh.Src}" alt="preview">`;
if (!mkr.getPopup()) {
mkr.bindPopup(photoPopup, {minWidth: width});
} else {
iconTh.OnLoad = (): void => {
obj.iconUrl = iconTh.Src;
};
mkr.setPopupContent(photoPopup);
}
};
if (photoTh.Available) {
setPopUpPhoto();
} else {
const noPhotoPopup = `<div class="lightbox-map-gallery-component-preview-loading"
style="width: ${width}px; height: ${height}px">
<span class="oi ${photoTh.Error ? 'oi-warning' : 'oi-image'}"
aria-hidden="true">
</span>
</div>`;
mkr.bindPopup(noPhotoPopup, {minWidth: width});
mkr.on('popupopen', () => {
photoTh.load();
photoTh.CurrentlyWaiting = true;
});
photoTh.OnLoad = setPopUpPhoto;
}
// Setting photo icon
if (Config.Client.Map.useImageMarkers === true) {
const iconTh = this.thumbnailService.getIcon(new MediaIcon(p));
this.thumbnailsOnLoad.push(iconTh);
iconTh.Visible = true;
const setIcon = () => {
mkr.setIcon(icon({
iconUrl: iconTh.Src,
iconSize: this.iconSize, // size of the icon
}));
};
if (iconTh.Available === true) {
setIcon();
} else {
iconTh.OnLoad = setIcon;
}
}
return obj;
});
if (this.gpxFiles) {
this.loadGPXFiles().catch(console.error);
@ -184,11 +252,10 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
}
hideImages(): void {
this.mapPhotos.forEach((mp): void => {
mp.iconThumbnail.destroy();
mp.preview.thumbnail.destroy();
this.thumbnailsOnLoad.forEach((th): void => {
th.destroy();
});
this.mapPhotos = [];
this.thumbnailsOnLoad = [];
}
@HostListener('window:keydown', ['$event'])
@ -206,20 +273,57 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
this.fullScreenService.showFullScreen(this.elementRef.nativeElement);
}
break;
case 'Escape': // escape
case 'Escape':
this.hide();
break;
}
}
onMapReady(map: Map): void {
this.leafletMap = map;
this.leafletMap.zoomControl.setPosition('bottomright');
this.mapLayerControl.addTo(this.leafletMap);
}
onLeafletZoom(): void {
(this.mapLayersControlOption.overlays.Photos.getLayers() as Marker[]).forEach(mkr => {
if (this.leafletMap.getZoom() < 15) {
mkr.getIcon().options.iconSize = this.smallIconSize;
mkr.setIcon(mkr.getIcon());
} else {
mkr.getIcon().options.iconSize = this.iconSize;
mkr.setIcon(mkr.getIcon());
}
});
}
private centerMap(): void {
if (this.mapPhotos.length > 0) {
this.yagaMap.fitBounds(this.mapPhotos as any);
}
this.leafletMap.fitBounds(
latLngBounds((this.mapLayersControlOption.overlays.Photos.getLayers() as Marker[])
.map(m => m.getLatLng())
)
);
}
private async loadGPXFiles(): Promise<void> {
this.paths = [];
this.mapLayersControlOption.overlays.Paths.clearLayers();
if (this.gpxFiles.length === 0) {
// remove from controls
this.mapLayerControl.removeLayer(this.mapLayersControlOption.overlays.Paths);
// remove from map
if (this.leafletMap) {
this.leafletMap.removeLayer(this.mapLayersControlOption.overlays.Paths);
}
} else {
// make sure it does not appear twice
this.mapLayerControl.removeLayer(this.mapLayersControlOption.overlays.Paths);
this.mapLayerControl.addOverlay(this.mapLayersControlOption.overlays.Paths, 'Paths');
if (this.leafletMap && !this.leafletMap.hasLayer(this.mapLayersControlOption.overlays.Paths)) {
this.leafletMap.addLayer(this.mapLayersControlOption.overlays.Paths);
}
}
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < this.gpxFiles.length; i++) {
const file = this.gpxFiles[i];
@ -230,19 +334,10 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
if (path.length === 0) {
continue;
}
this.paths.push(path as LatLng[]);
this.mapLayersControlOption.overlays.Paths.addLayer(marker(path[0] as LatLng));
this.mapLayersControlOption.overlays.Paths.addLayer(polyline(path as LatLng[]));
}
}
private getScreenWidth(): number {
return window.innerWidth;
}
private getScreenHeight(): number {
return window.innerHeight;
}
}
export interface MapPhoto {

View File

@ -1,4 +1,4 @@
.yaga-map{
.map{
/*z-index: 0;*/
width: 100%;
height: 100%;

View File

@ -1,26 +1,13 @@
<ng-template [ngIf]="mapPhotos.length>0">
<ng-template [ngIf]="markerLayer.length>0">
<app-gallery-map-lightbox [photos]="photos" [gpxFiles]="gpxFiles"></app-gallery-map-lightbox>
<div class="clickable" id="map" #map (click)="click()">
<span *ngIf="!EnableMapPreview" class="oi-map oi"></span>
<yaga-map
*ngIf="EnableMapPreview"
#yagaMap
[draggingEnabled]="false"
[keyboardEnabled]="false"
[tapEnabled]="false"
[boxZoomEnabled]="false"
[doubleClickZoomEnabled]="false">
<yaga-marker
*ngFor="let photo of mapPhotos"
[lat]="photo.lat"
[lng]="photo.lng">
</yaga-marker>
<yaga-attribution-control
prefix=""
[attributions]="mapService.ShortAttributions">
</yaga-attribution-control>
<yaga-tile-layer [url]="mapService.MapLayer"></yaga-tile-layer>
</yaga-map>
<div
class="map"
leaflet
[leafletOptions]="options"
[leafletLayers]="markerLayer"
(leafletMapReady)="onMapReady($event)">
</div>
</div>
</ng-template>

View File

@ -1,82 +1,74 @@
import {AfterViewInit, Component, ElementRef, Input, OnChanges, ViewChild} from '@angular/core';
import {Component, ElementRef, Input, OnChanges, ViewChild} from '@angular/core';
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
import {Dimension, IRenderable} from '../../../model/IRenderable';
import {GalleryMapLightboxComponent} from './lightbox/lightbox.map.gallery.component';
import {FileDTO} from '../../../../../common/entities/FileDTO';
import {MapService} from './map.service';
import {MapComponent} from '@yaga/leaflet-ng2';
import {Config} from '../../../../../common/config/public/Config';
import {LatLngLiteral, Map, MapOptions, Marker, marker, tileLayer} from 'leaflet';
@Component({
selector: 'app-gallery-map',
templateUrl: './map.gallery.component.html',
styleUrls: ['./map.gallery.component.css']
})
export class GalleryMapComponent implements OnChanges, IRenderable, AfterViewInit {
export class GalleryMapComponent implements OnChanges, IRenderable {
@Input() photos: PhotoDTO[];
@Input() gpxFiles: FileDTO[];
@ViewChild(GalleryMapLightboxComponent, {static: false}) mapLightbox: GalleryMapLightboxComponent;
mapPhotos: Array<{ lat: number, lng: number }> = [];
@ViewChild('map', {static: false}) mapElement: ElementRef;
@ViewChild('yagaMap', {static: false}) yagaMap: MapComponent;
// height: number = null;
leafletMap: Map;
options: MapOptions = {
zoomControl: false,
dragging: false,
keyboard: false,
tap: false,
doubleClickZoom: false,
boxZoom: false,
zoom: 0,
};
markerLayer: Marker[] = [];
constructor(public mapService: MapService) {
this.options.layers = [
tileLayer(mapService.MapLayer, {attribution: mapService.ShortAttributions})
];
}
get EnableMapPreview(): boolean {
/**
* Disabling map preview on IOS as safari has issues handling z-index of leaflet (yaga-maps)
* Details https://github.com/bpatrik/pigallery2/issues/155
* TODO: re enable it once yaga-maps is fixed
*/
const isIOS = [
'iPad Simulator',
'iPhone Simulator',
'iPod Simulator',
'iPad',
'iPhone',
'iPod'
].includes(navigator.platform)
// iPad on iOS 13 detection
|| (navigator.userAgent.includes('Mac') && 'ontouchend' in document);
return !isIOS;
onMapReady(map: Map): void {
this.leafletMap = map;
this.leafletMap.setView(this.markerLayer[0].getLatLng(), 99);
this.leafletMap.fitBounds(this.markerLayer.map((mp): [number, number] =>
[mp.getLatLng().lat, mp.getLatLng().lng] as [number, number]));
this.leafletMap.setZoom(0);
}
ngOnChanges(): void {
this.mapPhotos = this.photos.filter((p): number => {
this.markerLayer = this.photos.filter((p): number => {
return p.metadata && p.metadata.positionData && p.metadata.positionData.GPSData &&
p.metadata.positionData.GPSData.latitude && p.metadata.positionData.GPSData.longitude;
}).slice(0, Config.Client.Map.maxPreviewMarkers).map((p): { lng: number; lat: number } => {
return {
}).slice(0, Config.Client.Map.maxPreviewMarkers).map((p): Marker => {
return marker({
lat: p.metadata.positionData.GPSData.latitude,
lng: p.metadata.positionData.GPSData.longitude
};
} as LatLngLiteral);
});
if (this.yagaMap && this.mapPhotos.length > 0) {
this.yagaMap.setView(this.mapPhotos[0], 99);
this.yagaMap.fitBounds(this.mapPhotos.map((mp): [number, number] => [mp.lat, mp.lng] as [number, number]));
this.yagaMap.zoom = 0;
if (this.leafletMap && this.markerLayer.length > 0) {
this.options.center = this.markerLayer[0].getLatLng();
this.leafletMap.setView(this.markerLayer[0].getLatLng(), 99);
this.leafletMap.fitBounds(this.markerLayer.map((mp): [number, number] =>
[mp.getLatLng().lat, mp.getLatLng().lng] as [number, number]));
this.leafletMap.setZoom(0);
}
}
ngAfterViewInit(): void {
setTimeout((): void => {
// this.height = this.mapElement.nativeElement.clientHeight;
this.yagaMap.setView(this.mapPhotos[0], 99);
this.yagaMap.fitBounds(this.mapPhotos.map((mp): [number, number] => [mp.lat, mp.lng] as [number, number]));
this.yagaMap.zoom = 0;
}, 0);
}
click(): void {
this.mapLightbox.show(this.getDimension());
}

View File

@ -25,37 +25,33 @@ export class MapService {
];
}
public get ShortAttributions(): string[] {
const yaga = '<a href="https://yagajs.org" title="YAGA">YAGA</a>';
const lf = '<a href="https://leaflet-ng2.yagajs.org" title="Leaflet in Angular2">leaflet-ng2</a>';
public get ShortAttributions(): string {
const OSM = '<a href="https://www.openstreetmap.org/copyright">OSM</a>';
const MB = '<a href="https://www.mapbox.com/">Mapbox</a>';
if (Config.Client.Map.mapProvider === MapProviders.OpenStreetMap) {
return [yaga + ' | &copy; ' + OSM];
return ' &copy; ' + OSM;
}
if (Config.Client.Map.mapProvider === MapProviders.Mapbox) {
return [yaga + ' | ' + OSM + ' | ' + MB];
return OSM + ' | ' + MB;
}
return [yaga + ' | ' + lf];
return '';
}
public get Attributions(): string[] {
const yagalf = '<a href="https://yagajs.org" title="YAGA">YAGA</a> | ' +
'<a href="https://leaflet-ng2.yagajs.org" title="Leaflet in Angular2">leaflet-ng2</a>';
public get Attributions(): string {
const OSM = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>';
const MB = '&copy; <a href="https://www.mapbox.com/">Mapbox</a>';
if (Config.Client.Map.mapProvider === MapProviders.OpenStreetMap) {
return [yagalf + ' | ' + OSM];
return OSM;
}
if (Config.Client.Map.mapProvider === MapProviders.Mapbox) {
return [yagalf + ' | ' + OSM + ' | ' + MB];
return OSM + ' | ' + MB;
}
return [yagalf];
return '';
}
public get MapLayer(): string {

View File

@ -14,9 +14,7 @@
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-switch/3.3.4/css/bootstrap3/bootstrap-switch.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css"
integrity="sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA=="
crossorigin=""/>
<script>
var ServerInject = {user: <%- JSON.stringify(user); %>, ConfigInject: <%- JSON.stringify(Config); %>}

View File

@ -32,3 +32,4 @@ bs-dropdown-container {
transition-delay: 1s;
transition-property: opacity;
}