Add leaflet map with clustered photos

This commit is contained in:
Maciej Jur 2023-04-08 01:27:59 +02:00
parent a4bf1bb37a
commit 76290df734
8 changed files with 6672 additions and 1 deletions

3
.gitignore vendored
View file

@ -19,3 +19,6 @@ pnpm-debug.log*
# macOS-specific files
.DS_Store
# binary blobs
public/**/*.jpg

View file

@ -11,9 +11,13 @@
},
"dependencies": {
"astro": "^2.2.0",
"dayjs": "^1.11.7"
"dayjs": "^1.11.7",
"leaflet": "^1.9.3",
"leaflet.markercluster": "^1.5.3"
},
"devDependencies": {
"@types/leaflet": "^1.9.3",
"@types/leaflet.markercluster": "^1.5.1",
"sass": "^1.61.0"
}
}

View file

@ -7,8 +7,20 @@ dependencies:
dayjs:
specifier: ^1.11.7
version: 1.11.7
leaflet:
specifier: ^1.9.3
version: 1.9.3
leaflet.markercluster:
specifier: ^1.5.3
version: 1.5.3(leaflet@1.9.3)
devDependencies:
'@types/leaflet':
specifier: ^1.9.3
version: 1.9.3
'@types/leaflet.markercluster':
specifier: ^1.5.1
version: 1.5.1
sass:
specifier: ^1.61.0
version: 1.61.0
@ -660,6 +672,10 @@ packages:
resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==}
dev: false
/@types/geojson@7946.0.10:
resolution: {integrity: sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==}
dev: true
/@types/hast@2.3.4:
resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==}
dependencies:
@ -670,6 +686,18 @@ packages:
resolution: {integrity: sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA==}
dev: false
/@types/leaflet.markercluster@1.5.1:
resolution: {integrity: sha512-gzJzP10qO6Zkts5QNVmSAEDLYicQHTEBLT9HZpFrJiSww9eDAs5OWHvIskldf41MvDv1gbMukuEBQEawHn+wtA==}
dependencies:
'@types/leaflet': 1.9.3
dev: true
/@types/leaflet@1.9.3:
resolution: {integrity: sha512-Caa1lYOgKVqDkDZVWkto2Z5JtVo09spEaUt2S69LiugbBpoqQu92HYFMGUbYezZbnBkyOxMNPXHSgRrRY5UyIA==}
dependencies:
'@types/geojson': 7946.0.10
dev: true
/@types/mdast@3.0.11:
resolution: {integrity: sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==}
dependencies:
@ -1588,6 +1616,18 @@ packages:
engines: {node: '>=6'}
dev: false
/leaflet.markercluster@1.5.3(leaflet@1.9.3):
resolution: {integrity: sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==}
peerDependencies:
leaflet: ^1.3.1
dependencies:
leaflet: 1.9.3
dev: false
/leaflet@1.9.3:
resolution: {integrity: sha512-iB2cR9vAkDOu5l3HAay2obcUHZ7xwUBBjph8+PGtmW/2lYhbLizWtG7nTeYht36WfOslixQF9D/uSIzhZgGMfQ==}
dev: false
/load-yaml-file@0.2.0:
resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==}
engines: {node: '>=6'}

6442
public/static/map/data.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,94 @@
import L from 'leaflet';
import 'leaflet.markercluster'
interface Photo extends L.LatLngLiteral {
thumbnail: string;
photoUrl: string;
caption: string;
date: string;
}
L.Photo = L.FeatureGroup.extend({
options: {
icon: {
iconSize: [40, 40] as L.PointTuple
}
},
initialize: function (photos: Photo[], options: any) {
L.setOptions(this, options);
// @ts-ignore
L.FeatureGroup.prototype.initialize.call(this, photos);
},
addLayers: function (photos: Photo[]) {
for (const photo of photos)
this.addLayer(photo);
return this;
},
addLayer: function (photo: Photo) {
L.FeatureGroup.prototype.addLayer.call(this, this.createMarker(photo));
},
createMarker: function (photo: Photo) {
const marker = L.marker(photo, {
icon: L.divIcon(L.extend({
html: `<div style="background-image: url(${photo.thumbnail});"></div>`,
className: 'leaflet-marker-photo'
}, photo, this.options.icon)),
title: photo.caption || ''
});
// @ts-ignore
marker.photo = photo;
return marker;
}
});
L.photo = function (photos, options) {
return new L.Photo(photos, options);
};
if (L.MarkerClusterGroup) {
L.Photo.Cluster = L.MarkerClusterGroup.extend({
options: {
featureGroup: L.photo,
maxClusterRadius: 100,
showCoverageOnHover: false,
icon: { iconSize: [40, 40] as L.PointTuple },
iconCreateFunction: function(cluster: any) {
const markers = cluster.getAllChildMarkers();
return new L.DivIcon(L.extend({
html: `<div style="background-image: url(${markers[0].photo.thumbnail});"></div><b>${markers.length}</b>`,
className: 'leaflet-marker-photo',
}, this.icon));
},
},
initialize: function (options: any) {
options = L.Util.setOptions(this, options);
// @ts-ignore
L.MarkerClusterGroup.prototype.initialize.call(this);
this._photos = options.featureGroup(null, options);
},
add: function (photos: Photo) {
this.addLayer(this._photos.addLayers(photos));
return this;
},
clear: function () {
this._photos.clearLayers();
this.clearLayers();
}
});
L.photo.cluster = function (options: any) {
return new L.Photo.Cluster!(options);
};
}

View file

@ -0,0 +1,27 @@
.leaflet-marker-photo {
border: 2px solid #fff;
box-shadow: 3px 3px 10px #888;
div {
width: 100%;
height: 100%;
background-size: cover;
background-position: center center;
background-repeat: no-repeat;
}
b {
position: absolute;
top: -7px;
right: -11px;
color: #555;
background-color: #fff;
border-radius: 8px;
height: 12px;
min-width: 12px;
line-height: 12px;
text-align: center;
padding: 3px;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
}

12
src/assets/leaflet-photo/types.d.ts vendored Normal file
View file

@ -0,0 +1,12 @@
import 'leaflet';
declare module 'leaflet' {
class Photo extends L.FeatureGroup {
static Cluster?: { new(...args: any[]): any } & L.Class;
}
let photo: {
(photos: Photo[], options: any): Photo;
cluster?: (options?: any) => any;
};
}

49
src/pages/map.astro Normal file
View file

@ -0,0 +1,49 @@
---
import Base from "../layouts/Base.astro";
import 'leaflet/dist/leaflet.css';
import 'leaflet.markercluster/dist/MarkerCluster.css';
import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
import '../assets/leaflet-photo/styles.scss';
---
<Base>
<div id="map" style="height: 100%; width: 100%"></div>
<script>
import L from 'leaflet';
import 'leaflet.markercluster';
import '../assets/leaflet-photo';
const template = `
<div class="popup">
<a href="{photo}">
<img width="{width}" height="{height}" src="{photo}" alt="" />
<div class="meta">
<span class="date">{date}</span><span class="caption">{caption}</span>
</div>
</a>
</div>`;
const map = L.map('map').setView([51.85, 16.57], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
const photoLayer = L.photo.cluster!().on('click', function(evt: any) {
evt.layer.bindPopup(L.Util.template(template, evt.layer.photo)).openPopup();
});
async function loadData() {
const data = await fetch('/static/map/data.json');
if (!data.ok) return;
return await data.json()
}
// Add photos to the map
loadData().then(data => photoLayer.add(data).addTo(map));
</script>
<div slot="footer"></div>
</Base>