1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-01-10 04:07:35 +02:00

Implementing smarter map layers for paths. It can now recognize sport and transportation #647

This commit is contained in:
Patrik J. Braun 2023-03-20 22:20:43 +01:00
parent 1766cf2062
commit 61970c2025
7 changed files with 194 additions and 57 deletions

View File

@ -100,6 +100,10 @@ export class Utils {
return size.toFixed(2) + postFixes[index];
}
static getUnique(arr: any[]) {
return arr.filter((value, index, arr) => arr.indexOf(value) === index);
}
static createRange(from: number, to: number): Array<number> {
const arr = new Array(to - from + 1);
let c = to - from + 1;
@ -129,7 +133,7 @@ export class Utils {
url += part + '/';
}
url = url.replace(/(https?:\/\/)|(\/){2,}/g, "$1$2")
url = url.replace(/(https?:\/\/)|(\/){2,}/g, '$1$2');
if (url.trim() === '') {
url = './';

View File

@ -107,6 +107,7 @@ import {SharingsListComponent} from './ui/settings/sharings-list/sharings-list.c
import {ThemeService} from './model/theme.service';
import {StringifyConfigPriority} from './pipes/StringifyConfigPriority';
import {StringifySearchType} from './pipes/StringifySearchType';
import {MarkerFactory} from './ui/gallery/map/MarkerFactory';
@Injectable()
export class MyHammerConfig extends HammerGestureConfig {
@ -137,22 +138,8 @@ export class CustomUrlSerializer implements UrlSerializer {
}
}
// Fixes Leaflet icon path issue:
// https://stackoverflow.com/questions/41144319/leaflet-marker-not-found-production-env
const iconRetinaUrl = 'assets/marker-icon-2x.png';
const iconUrl = 'assets/marker-icon.png';
const shadowUrl = 'assets/marker-shadow.png';
const iconDefault = icon({
iconRetinaUrl,
iconUrl,
shadowUrl,
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
tooltipAnchor: [16, -28],
shadowSize: [41, 41],
});
Marker.prototype.options.icon = iconDefault;
Marker.prototype.options.icon = MarkerFactory.defIcon;
@NgModule({
imports: [

View File

@ -0,0 +1,37 @@
import {DivIcon, setOptions} from 'leaflet';
export interface SvgIconOptions {
color?: string;
svgPath?: string;
width?: number;
small?: boolean;
}
const SvgIcon: { new(options?: SvgIconOptions): DivIcon } = DivIcon.extend({
initialize: function(options: SvgIconOptions = {}) {
options.color = options.color || 'var(--bs-primary)';
options.svgPath = options.svgPath || 'M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512z';
options.width = options.width || 512;
const svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + options.width + ' 512"><path fill="' + options.color + '" d="' + options.svgPath + '"/></svg>';
setOptions(this, {
iconSize: options.small ? [15, 15] : [30, 30],
iconAnchor: options.small ? [15, 28] : [15, 35],
popupAnchor: options.small ? [0, -15] : [0, -30],
className: 'custom-div-icon' + (options.small ? ' marker-svg-small' : ''),
html: '<div class="marker-svg-wrapper"><div class="marker-svg-shadow"></div>' +
'<div class="marker-svg-pin" style="border-color: ' + options.color + '">' +
'</div>' + svg + '</div>',
});
}
});
export class MarkerFactory {
public static readonly defIcon = MarkerFactory.getSvgIcon();
public static readonly defIconSmall = MarkerFactory.getSvgIcon({small: true});
static getSvgIcon(options?: SvgIconOptions) {
return new SvgIcon(options);
}
}

View File

@ -73,6 +73,48 @@
opacity: 1.0;
}
::ng-deep .marker-svg-small .marker-svg-wrapper{
width: 30px;
height: 30px;
transform: scale(0.5);
}
::ng-deep .marker-svg-pin {
width: 30px;
height: 30px;
left: 50%;
top: 50%;
margin: -15px 0 0 -15px;
border-radius: 50% 50% 50% 0;
position: absolute;
border: 3px solid;
transform: rotate(-45deg);
background-color: rgba(248, 249, 250, 0.9);
}
::ng-deep .marker-svg-shadow {
content: "";
background: rgba(128, 128, 128, 0.4);
width: 20px;
height: 10px;
border-radius: 100%;
position: absolute;
top: 31px;
left: 5px
}
::ng-deep .custom-div-icon svg {
position: absolute;
width: 16px !important;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
text-align: center;
}
::ng-deep .lightbox-map-gallery-component-preview-loading {
background-color: #bbbbbb;
color: #7f7f7f;

View File

@ -14,6 +14,7 @@ import {
control,
Control,
divIcon,
DivIcon,
icon,
latLng,
latLngBounds,
@ -33,6 +34,8 @@ import {
import {LeafletControlLayersConfig} from '@asymmetrik/ngx-leaflet';
import {ThemeService} from '../../../../model/theme.service';
import {Subscription} from 'rxjs';
import {MarkerFactory} from '../MarkerFactory';
@Component({
selector: 'app-gallery-map-lightbox',
@ -76,7 +79,10 @@ export class GalleryMapLightboxComponent implements OnChanges, OnDestroy {
);
private usedIconSize = this.iconSize;
private mapLayersControlOption: LeafletControlLayersConfig & {
overlays: { Photos: MarkerClusterGroup; Paths: LayerGroup };
overlays: {
Photos: MarkerClusterGroup;
[name: string]: LayerGroup;
};
} = {
baseLayers: {},
overlays: {
@ -104,10 +110,37 @@ export class GalleryMapLightboxComponent implements OnChanges, OnDestroy {
});
},
}),
Paths: layerGroup([]),
},
};
private mapLayerControl: Control.Layers;
// ordered list
private pathLayersConfigOrdered = [
{
name: $localize`Transportation`,
matchers: [/flight/gi, /flying/gi, /drive/gi, /driving/gi] as RegExp[],
layer: layerGroup([]),
theme: {color: 'var(--bs-orange)', dashArray: '4 8'},
icon: null as DivIcon,
svgIcon: {
width: 567,
path: 'M482.3 192c34.2 0 93.7 29 93.7 64c0 36-59.5 64-93.7 64l-116.6 0L265.2 495.9c-5.7 10-16.3 16.1-27.8 16.1l-56.2 0c-10.6 0-18.3-10.2-15.4-20.4l49-171.6L112 320 68.8 377.6c-3 4-7.8 6.4-12.8 6.4l-42 0c-7.8 0-14-6.3-14-14c0-1.3 .2-2.6 .5-3.9L32 256 .5 145.9c-.4-1.3-.5-2.6-.5-3.9c0-7.8 6.3-14 14-14l42 0c5 0 9.8 2.4 12.8 6.4L112 192l102.9 0-49-171.6C162.9 10.2 170.6 0 181.2 0l56.2 0c11.5 0 22.1 6.2 27.8 16.1L365.7 192l116.6 0z'
}
},
{
name: $localize`Sport`,
matchers: [/run/gi, /walk/gi, /hike/gi, /hiking/gi, /bike/gi, /biking/gi, /cycling/gi] as RegExp[],
layer: layerGroup([]),
theme: {color: 'var(--bs-primary)'},
svgIcon: {
width: 417,
path: 'M320 48a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM125.7 175.5c9.9-9.9 23.4-15.5 37.5-15.5c1.9 0 3.8 .1 5.6 .3L137.6 254c-9.3 28 1.7 58.8 26.8 74.5l86.2 53.9-25.4 88.8c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l28.7-100.4c5.9-20.6-2.6-42.6-20.7-53.9L238 299l30.9-82.4 5.1 12.3C289 264.7 323.9 288 362.7 288H384c17.7 0 32-14.3 32-32s-14.3-32-32-32H362.7c-12.9 0-24.6-7.8-29.5-19.7l-6.3-15c-14.6-35.1-44.1-61.9-80.5-73.1l-48.7-15c-11.1-3.4-22.7-5.2-34.4-5.2c-31 0-60.8 12.3-82.7 34.3L57.4 153.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l23.1-23.1zM91.2 352H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h69.6c19 0 36.2-11.2 43.9-28.5L157 361.6l-9.5-6c-17.5-10.9-30.5-26.8-37.9-44.9L91.2 352z'
}
},
{
name: $localize`Other paths`,
matchers: null as RegExp[], layer: layerGroup([]), theme: {color: 'var(--bs-secondary)'}
}
];
mapLayerControl: Control.Layers;
private thumbnailsOnLoad: ThumbnailBase[] = [];
private startPosition: Dimension = null;
private leafletMap: Map;
@ -119,10 +152,9 @@ export class GalleryMapLightboxComponent implements OnChanges, OnDestroy {
public mapService: MapService,
private themeService: ThemeService
) {
this.mapOptions.layers = [
this.mapLayersControlOption.overlays.Photos,
this.mapLayersControlOption.overlays.Paths,
];
this.setUpPathLayers();
this.mapOptions.layers = [this.mapLayersControlOption.overlays.Photos];
this.pathLayersConfigOrdered.forEach(pl => this.mapOptions.layers.push(pl.layer));
for (let i = 0; i < mapService.Layers.length; ++i) {
const l = mapService.Layers[i];
const tl = tileLayer(l.url, {attribution: mapService.Attributions});
@ -149,6 +181,13 @@ export class GalleryMapLightboxComponent implements OnChanges, OnDestroy {
this.darkModeSubscription = this.themeService.darkMode.subscribe(this.selectBaseLayer);
}
setUpPathLayers() {
this.pathLayersConfigOrdered.forEach(pl => {
pl.icon = MarkerFactory.getSvgIcon({color: pl.theme.color, svgPath: pl.svgIcon?.path, width: pl.svgIcon?.width});
this.mapLayersControlOption.overlays[pl.name] = pl.layer;
});
}
ngOnDestroy(): void {
this.darkModeSubscription.unsubscribe();
}
@ -324,6 +363,7 @@ export class GalleryMapLightboxComponent implements OnChanges, OnDestroy {
photoTh.OnLoad = setPopUpPhoto;
}
mkr.setIcon(MarkerFactory.defIcon);
// Setting photo icon
if (Config.Map.useImageMarkers === true) {
mkr.on('add', () => {
@ -446,31 +486,19 @@ export class GalleryMapLightboxComponent implements OnChanges, OnDestroy {
}
private async loadGPXFiles(): Promise<void> {
this.mapLayersControlOption.overlays.Paths.clearLayers();
this.pathLayersConfigOrdered.forEach(p => p.layer.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);
}
this.pathLayersConfigOrdered.forEach(p => {
// remove from controls
this.mapLayerControl.removeLayer(p.layer);
// remove from map
if (this.leafletMap) {
this.leafletMap.removeLayer(p.layer);
}
});
return;
}
// eslint-disable-next-line @typescript-eslint/prefer-for-of
@ -481,19 +509,57 @@ export class GalleryMapLightboxComponent implements OnChanges, OnDestroy {
// check race condition
return;
}
const pathLayer = this.pathLayersConfigOrdered.find((pl) => {
return pl.matchers === null || // null matchers match everything
(parsedGPX.name &&
pl.matchers.findIndex(m => m.test(parsedGPX.name)) !== -1);
}) || this.pathLayersConfigOrdered[0];
if (parsedGPX.path.length !== 0) {
// render the beginning of the path with a marker
this.mapLayersControlOption.overlays.Paths.addLayer(
marker(parsedGPX.path[0])
);
this.mapLayersControlOption.overlays.Paths.addLayer(
polyline(parsedGPX.path, {smoothFactor: 3})
const mkr = marker(parsedGPX.path[0]);
pathLayer.layer.addLayer(mkr);
mkr.setIcon(pathLayer.icon);
// Setting popup photo
mkr.bindPopup(file.name + ': ' + parsedGPX.name);
pathLayer.layer.addLayer(
polyline(parsedGPX.path, {
smoothFactor: 3,
interactive: false,
color: pathLayer?.theme?.color,
dashArray: pathLayer?.theme?.dashArray
})
);
}
parsedGPX.markers.forEach((mc) => {
this.mapLayersControlOption.overlays.Paths.addLayer(marker(mc));
const mkr = marker(mc);
mkr.setIcon(pathLayer.icon);
pathLayer.layer.addLayer(mkr);
mkr.bindPopup($localize`Latitude` + ': ' + mc.lat + ', ' + $localize`longitude` + ': ' + mc.lng);
});
}
// Add layer to the map
this.pathLayersConfigOrdered.filter(pl => pl.layer.getLayers().length > 0).forEach((pl) => {
// make sure it does not appear twice
this.mapLayerControl.removeLayer(
pl.layer
);
this.mapLayerControl.addOverlay(
pl.layer,
pl.name
);
if (
this.leafletMap &&
!this.leafletMap.hasLayer(pl.layer)
) {
this.leafletMap.addLayer(pl.layer);
}
});
}
}

View File

@ -8,6 +8,7 @@ import {Config} from '../../../../../common/config/public/Config';
import {LatLngLiteral, Map, MapOptions, Marker, marker, tileLayer, TileLayer} from 'leaflet';
import {ThemeService} from '../../../model/theme.service';
import {Subscription} from 'rxjs';
import {MarkerFactory} from './MarkerFactory';
@Component({
selector: 'app-gallery-map',
@ -113,7 +114,7 @@ export class GalleryMapComponent implements OnChanges, IRenderable {
return marker({
lat: p.metadata.positionData.GPSData.latitude,
lng: p.metadata.positionData.GPSData.longitude,
} as LatLngLiteral);
} as LatLngLiteral).setIcon(MarkerFactory.defIconSmall);
});
if (this.leafletMap && this.markerLayer.length > 0) {

View File

@ -100,7 +100,7 @@ export class MapService {
public async getMapCoordinates(
file: FileDTO
): Promise<{ path: LatLngLiteral[]; markers: LatLngLiteral[] }> {
): Promise<{ name: string, path: LatLngLiteral[]; markers: LatLngLiteral[] }> {
const filePath = Utils.concatUrls(
file.directory.path,
file.directory.name,
@ -121,8 +121,8 @@ export class MapService {
}
return ret;
};
return {
name: gpx.getElementsByTagName('name')?.[0]?.textContent || '',
path: getCoordinates('trkpt'),
markers: getCoordinates('wpt'),
};