Unified maps with a single component

This commit is contained in:
Alexis
2025-01-10 15:36:58 +01:00
parent 6ba8f10f7e
commit 7706a193c7
12 changed files with 122 additions and 2040 deletions

View File

@@ -1,28 +0,0 @@
---
import type { MapMarker, PlayerMarker } from "@/types/Leaflet";
import SearchMarkers from "./overlay/SearchMarkers.vue";
import markersData from '@/components/maps/data/aldys/markers.json'
import playersData from '@/components/maps/data/aldys/players.json'
const markers = markersData.data as MapMarker[]
const players = playersData as PlayerMarker
---
<div class="world-overlay">
<SearchMarkers client:load markers={markers} players={players} mapKey="aldys" />
</div>
<style lang="scss">
.world-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
z-index: 10;
padding: 10px;
pointer-events: none;
}
</style>

View File

@@ -1,644 +0,0 @@
---
import type { MapMarker, PlayerMarker } from '@/types/Leaflet';
import markersData from '@/components/maps/data/bamast/markers.json'
import playersData from '@/components/maps/data/bamast/players.json'
import MarkerCreator from './overlay/MarkerCreator.vue';
const markers = markersData.data as MapMarker[]
const players = playersData as PlayerMarker
---
<div class="world-wrapper">
<div id="world"></div>
<MarkerCreator client:load mapKey="bamast" />
</div>
<script lang="ts" define:vars={{ markers, players }} defer>
const mapHeight = 18342; //
const mapWidth = 15000; // Find these in the zoomify / original image data
// Units used for convertions to avoid changing original marker coords
const xRatio = .68447
const yRatio = .68447
const zoomRatio = .75
const xOffset = 0
const yOffset = -27.63442
/**
* LEAFLET MAP PARAMERTERS
*/
const minZoom = 4.7
const maxZoom = 6.7 // Looks best with zoomify image
const flyToDuration = 1.2 // In seconds
const flyToZoomLevel = 6.5
const mobileZoomLevels = {
minZoom: 2.5,
maxZoom: 6.7
}
const inertiaDeceleration = 2000
const maxBoundsPadding = 10
const maxBoundsViscosity = .75
const mapEl = document.getElementById('world')
// Initializes the map
const map = L.map('world', {
crs: L.CRS.Simple,
maxZoom,
minZoom,
zoom: minZoom,
zoomSnap: 0,
zoomControl: false,
inertiaDeceleration,
maxBoundsViscosity
})
/**
* MOBILE JS
*/
// Breakpoints
const desktopMQ = window.matchMedia('(min-width: 900px)')
function handleMapResize(e) {
if (e.matches) {
map.setMaxZoom(maxZoom)
map.setMinZoom(minZoom)
} else {
map.setMaxZoom(mobileZoomLevels.maxZoom)
map.setMinZoom(mobileZoomLevels.minZoom)
}
}
desktopMQ.addEventListener('change', handleMapResize)
handleMapResize(desktopMQ)
// Immediately set center of map
L.control.zoom({
position: 'bottomright',
}).addTo(map)
// Layers
const baseTileLayer = L.tileLayer.zoomify('/zoomify/bamast/{g}/{z}-{x}-{y}.jpg', {
width: mapWidth,
height: mapHeight,
}).addTo(map)
const layerGroups = {
players: L.layerGroup(),
capitals: L.layerGroup(),
cities: L.layerGroup(),
towns: L.layerGroup(),
landmarks: L.layerGroup(),
quests: L.layerGroup(),
}
/**
* Build all markers and their related info
*/
for (let i = 0; i < markers.length; i++) {
const m = markers[i]
const coords = [convertYToScale(m.markerCoords.y), convertXToScale(m.markerCoords.x)]
let markerIcon
let markerIconOverride
switch (m.group) {
case "capitals":
markerIcon = L.icon({
iconUrl: `/icon/castle.png`,
shadowUrl: `/icon/castle-shadow.png`,
iconSize: [25, 25],
shadowSize: [35, 30],
iconAnchor: [12.5, 8],
shadowAnchor: [11, 12]
})
break
case "cities":
markerIcon = L.icon({
iconUrl: `/icon/circle.png`,
iconSize: [12, 12],
})
break
case "landmarks":
markerIcon = L.icon({
iconUrl: `/icon/monument.png`,
shadowUrl: `/icon/monument-shadow.png`,
iconSize: [18, 18],
shadowSize: [32, 14],
iconAnchor: [12.5, 8],
shadowAnchor: [15, 6]
})
break
case "quests":
markerIcon = L.icon({
iconUrl: `/icon/flag.png`,
shadowUrl: `/icon/flag-shadow.png`,
iconSize: [20, 23],
shadowSize: [35, 30],
iconAnchor: [12.5, 8],
shadowAnchor: [11, 12]
})
break
case "towns":
default:
iconKey = "house"
break
}
// Handle icon override if it's specified
markerIconOverride = markerIcon
if (m.icon) {
markerIconOverride = L.icon({
iconUrl: `/icon/${m.icon}.png`,
shadowUrl: `/icon/${m.icon}-shadow.png`,
iconSize: [25, 25],
shadowSize: [35, 30],
iconAnchor: [12.5, 8],
shadowAnchor: [11, 12]
})
}
const marker = L.marker(coords, { icon: markerIconOverride }).addTo(map)
// Build popup content
let popupContent = ""
if (m.cover && m.coverAuthor && m.coverLink) {
const figureClass = m.coverPortrait ? 'portrait' : 'landscape';
const figureWidth = m.coverPortrait ? 210 : 420;
popupContent += `
<figure class="${figureClass}">
<img src="/images/cover/${m.cover}" alt="${m.title}" width="${figureWidth}" loading="lazy" />
<figcaption>
<cite>par <a href="${m.coverLink}" target="_blank">${m.coverAuthor}</a></cite>
</figcaption>
</figure>
`
}
if (m.link) {
popupContent += `
<a href="${m.link}" target="_blank" class="title">
${m.title}
</a>
`
} else {
popupContent += `
<strong class="title">
${m.title}
</strong>
`
}
if (m.description) {
popupContent += `<p>${m.description}</p>`
}
if (m.mapId) {
popupContent += `
<a href="${m.mapId}" class="title">
Voir la carte détaillée
</a>
`
}
marker.bindPopup(popupContent).openPopup()
const displayTooltip = m.group === 'capitals'
if (displayTooltip) {
L.tooltip({ permanent: true, direction: 'top', offset: [0, 2], content: m.title, className: "capital-name", opacity: 1 }).setLatLng(coords).addTo(map)
}
marker.addTo(layerGroups[m.group])
/**
* Fly to specific marker
*/
document.addEventListener(`fly-to-${m.title}`, (e) => {
map.flyTo(coords, flyToZoomLevel, { duration: flyToDuration })
setTimeout(() => {
marker.openPopup()
}, flyToDuration * 1000)
})
}
/**
* Add player's position marker
*/
if ( players && Object.keys(players).length > 0 ) {
const playersPosition =[
convertYToScale(players.markerCoords.y),
convertXToScale(players.markerCoords.x)
]
const playerIcon = L.icon({
iconUrl: `/icon/location-pin.png`,
shadowUrl: `/icon/location-pin-shadow.png`,
iconSize: [18, 24],
shadowSize: [20, 16],
iconAnchor: [11, 8],
shadowAnchor: [3, 0],
})
const playerMarker = L.marker(playersPosition, { icon: playerIcon }).addTo(map)
playerMarker.addTo(layerGroups['players'])
const popupContent = `
<strong class="title">
${players.title}
</strong>
<p>${players.description}</p>
`
playerMarker.bindPopup(popupContent).openPopup()
document.addEventListener('fly-to-players', () => {
map.flyTo(playersPosition, flyToZoomLevel, { duration: flyToDuration })
setTimeout(() => {
playerMarker.openPopup()
}, flyToDuration * 1000)
})
}
map.fitBounds(baseTileLayer.getBounds())
/**
* RULER
*/
/**
* Workaround specific to leaflet version 1.8.0
* See https://github.com/ljagis/leaflet-measure/issues/171 for details
*/
L.Control.Measure.include({
// set icon on the capture marker
_setCaptureMarkerIcon: function () {
// disable autopan
this._captureMarker.options.autoPanOnFocus = false
// default function
this._captureMarker.setIcon(
L.divIcon({
iconSize: this._map.getSize().multiplyBy(2)
})
)
},
})
const rulerDistanceRatio = 2
const rulerOptions = {
position: 'bottomleft',
primaryLengthUnit: 'kilometers',
secondaryLengthUnit: 'days',
primaryAreaUnit: 'hectares',
units: {
kilometers: {
factor: 0.00013 * rulerDistanceRatio,
display: 'Kilomètres',
},
days: {
factor: 0.00000325 * rulerDistanceRatio,
display: 'Jours de marche',
decimals: 1
}
}
}
const measureControl = L.control.measure(rulerOptions)
measureControl.addTo(map)
map.setMaxBounds(
[
[
baseTileLayer.getBounds().getSouth() - maxBoundsPadding,
baseTileLayer.getBounds().getWest() - maxBoundsPadding
],
[
baseTileLayer.getBounds().getNorth() + maxBoundsPadding,
baseTileLayer.getBounds().getEast() + maxBoundsPadding
]
]
)
/**
* CUSTOM MARKERS
*/
// List to be persisted in localStorage
let customMarkersList = []
/**
* Function that saves the customMarkersList object into the localStorage
* It also sends an event to refresh data on other components that use localStorage
*/
function saveCustomMarkers() {
localStorage.setItem('custom-markers-bamast', JSON.stringify(customMarkersList))
const refreshCustomMarkersEvent = new CustomEvent('refresh-custom-markers', { bubbles: true })
mapEl.dispatchEvent(refreshCustomMarkersEvent)
}
// Stores last map coords on context
map.addEventListener('contextmenu', storeLastHeldPositions)
map.addEventListener('click', storeLastHeldPositions)
function storeLastHeldPositions(e) {
localStorage.setItem('lastHeldXPosition', e.latlng.lng)
localStorage.setItem('lastHeldYPosition', e.latlng.lat)
}
/**
* Function that creates custom markers and places them on the map
* @param {string} markerTitle
*/
function addCustomMarker(markerTitle) {
const lon = Number(localStorage.getItem('lastHeldXPosition'))
const lat = Number(localStorage.getItem('lastHeldYPosition'))
const customMarkerIconOptions = {
iconUrl: `/icon/push-pin.png`,
shadowUrl: `/icon/push-pin-shadow.png`,
iconSize: [20, 20],
shadowSize: [30, 25],
iconAnchor: [0, 13],
shadowAnchor: [0, 15],
className: "fade-in"
}
const customMarkerIcon = L.icon(customMarkerIconOptions)
const customMarkerCoords = [lat, lon]
// Sticks the custom marker on the map
const customMarker = L.marker(customMarkerCoords, { icon: customMarkerIcon }).addTo(map)
if (markerTitle) {
const popupContent = `
${markerTitle}
`
customMarker.bindPopup(popupContent, { offset: [5, -5] }).openPopup()
}
/**
* Register event listener to fly to this custom marker
* TODO : Refactor these functions with the loadCustomMarkersFromStorage implementation
*/
document.addEventListener(`fly-to-${markerTitle}`, (e) => {
map.flyTo(customMarkerCoords, flyToZoomLevel, { duration: flyToDuration })
setTimeout(() => {
customMarker.openPopup()
}, flyToDuration * 1000)
})
/**
* Register event listener to remove this custom marker
* TODO : Refactor these functions with the loadCustomMarkersFromStorage implementation
*/
document.addEventListener(`delete-${markerTitle}`, () => {
removeCustomMarker(customMarker, markerTitle)
})
customMarkersList.push({
lat,
lon,
title: markerTitle,
icon: customMarkerIconOptions,
group: "custom"
})
saveCustomMarkers()
}
// Listeners for the custom add marker event
// See MarkerCreator.vue component for more details
document.addEventListener('add-custom-marker', (e) => {
addCustomMarker(e.detail.title)
})
/**
* Load all custom markers onto the map
*/
function loadCustomMarkersFromStorage() {
const customMarkersData = JSON.parse(localStorage.getItem('custom-markers-bamast'))
if (!customMarkersData) return
customMarkersList = customMarkersData
for (let i = 0; i < customMarkersData.length; i++) {
const m = customMarkersData[i];
const savedMarkerCoords = [m.lat, m.lon]
const savedMarker = L.marker(savedMarkerCoords, { icon: L.icon(m.icon) }).addTo(map)
if (m.title) {
const popupContent = `
${m.title}
`
savedMarker.bindPopup(popupContent, { offset: [5, -5] })
}
/**
* Register event listener to fly to this custom marker
* TODO : Refactor these functions with the addCustomMarker implementation
*/
document.addEventListener(`fly-to-${m.title}`, (e) => {
map.flyTo(savedMarkerCoords, flyToZoomLevel, { duration: flyToDuration })
setTimeout(() => {
savedMarker.openPopup()
}, flyToDuration * 1000)
})
/**
* Register event listener to remove this custom marker
* TODO : Refactor these functions with the addCustomMarker implementation
*/
document.addEventListener(`delete-${m.title}`, () => {
removeCustomMarker(savedMarker, m.title)
})
}
}
loadCustomMarkersFromStorage()
/**
* Function that deletes a specific marker from storage and removes it from the map
* @param {Marker} marker Leaflet marker object
* @param {string} markerTitle Key for custom marker
*/
function removeCustomMarker(marker, markerTitle) {
marker.remove()
customMarkersList = customMarkersList.filter(m => {
return m.title !== markerTitle
})
saveCustomMarkers()
}
/**
* URL PARAMETERS
*/
/**
* From the leaflet setup, get a working param config
*
* @returns Latitude, Longitude and zoom level of the map
*/
function getURLFromSetup() {
const centerOfMap = map.getCenter()
const lat = centerOfMap.lat
const lon = centerOfMap.lng
const zoom = parseFloat(map.getZoom().toFixed(1))
return { lat, lon, zoom }
}
/**
* Get leaflet coords from URL params
*
* @returns URLSearchParams
*/
function getCoordsSetupFromURL() {
const params = new URL(document.location).searchParams
return params
}
/**
* Check if the params in the URL can be parsed as leaflet coordinates
*
* @return boolean
*/
function hasInitialSetup() {
const params = new URL(document.location).searchParams
return params.has('lat') && params.has('lon')
}
/**
* Function to refresh the URL params
*
* @returns void
*/
function setupToURL() {
const setup = getURLFromSetup()
const newParams = new URLSearchParams(setup)
const newUrl = new URL(window.location.href)
newUrl.search = newParams.toString()
window.history.replaceState(null, '', newUrl.toString())
}
/**
* Uses the URL params to setup the leaflet map
*/
function setupFromURL() {
const setup = getCoordsSetupFromURL()
map.setView({ lat: setup.get('lat'), lng: setup.get('lon') }, setup.get('zoom') | minZoom, { animate: false })
}
// Check if the map has initial params in the URL
// If not, just setup the default URL
if (hasInitialSetup()) {
setupFromURL()
} else {
setupToURL()
}
// When the map stops moving, update the URL to keep it up to date
map.on('moveend', () => {
setupToURL()
})
/**
* UTILITIES
*/
/**
* Convert x coords to scale with ratio, zoom and offset
* @param {number} x
*/
function convertXToScale(x) {
return (x * xRatio * zoomRatio) + xOffset
}
/**
* Convert y coords to scale with ratio, zoom and offset
* @param {number} y
*/
function convertYToScale(y) {
return (y * yRatio * zoomRatio) + yOffset
}
/**
* Convert leaflet scale to x with ratio, zoom and offset
*
* It is basically the mathematically inverse function of convertXToScale()
* @param {number} y
*/
function convertScaleToX(x) {
return (x - xOffset) / (xRatio * zoomRatio)
}
/**
* Convert leaflet scale to y with ratio, zoom and offset
*
* It is basically the mathematically inverse function of convertYToScale()
* @param {number} y
*/
function convertScaleToY(y) {
return (y - yOffset) / (yRatio * zoomRatio)
}
/**
* DEBUGS
*/
/**
* Get relevant map info
*/
map.addEventListener('click', (event) => {
const lat = convertScaleToY(event.latlng.lat)
const lon = convertScaleToX(event.latlng.lng)
console.group('Point data')
console.log("Latitude :", lat)
console.log("Longitude :", lon)
console.log("Zoom :", map.getZoom())
console.groupEnd()
})
</script>
<style lang="scss">
html,
body {
height: 100vh;
margin: 0;
position: relative;
}
.world-wrapper {
position: fixed;
inset: 0;
z-index: 0;
isolation: isolate;
#world {
height: 100%;
width: 100%;
}
}
.leaflet-container {
aspect-ratio: 1551 / 1605;
}
</style>

View File

@@ -1,28 +0,0 @@
---
import type { MapMarker, PlayerMarker } from "@/types/Leaflet";
import SearchMarkers from "./overlay/SearchMarkers.vue";
import markersData from '@/components/maps/data/bamast/markers.json'
import playersData from '@/components/maps/data/bamast/players.json'
const markers = markersData.data as MapMarker[]
const players = playersData as PlayerMarker
---
<div class="world-overlay">
<SearchMarkers client:load markers={markers} players={players} mapKey="bamast" />
</div>
<style lang="scss">
.world-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
z-index: 10;
padding: 10px;
pointer-events: none;
}
</style>

View File

@@ -1,637 +0,0 @@
---
import type { MapMarker, PlayerMarker } from '@/types/Leaflet';
import MarkerCreator from './overlay/MarkerCreator.vue';
const markers = [] as MapMarker[]
const players = {} as PlayerMarker
---
<div class="world-wrapper">
<div id="world"></div>
<MarkerCreator client:load mapKey="aldys" />
</div>
<script lang="ts" define:vars={{ markers, players }} defer>
const mapHeight = 3710; //
const mapWidth = 4521; // Find these in the zoomify / original image data
// Units used for convertions to avoid changing original marker coords
const xRatio = .68447
const yRatio = .68447
const zoomRatio = .75
const xOffset = 0
const yOffset = -27.63442
/**
* LEAFLET MAP PARAMERTERS
*/
const minZoom = 3.5
const maxZoom = 6.7 // Looks best with zoomify image
const flyToDuration = 1.2 // In seconds
const flyToZoomLevel = 6.5
const mobileZoomLevels = {
minZoom: 2.5,
maxZoom: 6.7
}
const inertiaDeceleration = 2000
const maxBoundsPadding = 10
const maxBoundsViscosity = .75
const mapEl = document.getElementById('world')
// Initializes the map
const map = L.map('world', {
crs: L.CRS.Simple,
maxZoom,
minZoom,
zoom: minZoom,
zoomSnap: 0,
zoomControl: false,
inertiaDeceleration,
maxBoundsViscosity
})
/**
* MOBILE JS
*/
// Breakpoints
const desktopMQ = window.matchMedia('(min-width: 900px)')
function handleMapResize(e) {
if (e.matches) {
map.setMaxZoom(maxZoom)
map.setMinZoom(minZoom)
} else {
map.setMaxZoom(mobileZoomLevels.maxZoom)
map.setMinZoom(mobileZoomLevels.minZoom)
}
}
desktopMQ.addEventListener('change', handleMapResize)
handleMapResize(desktopMQ)
// Immediately set center of map
L.control.zoom({
position: 'bottomright',
}).addTo(map)
// Layers
const baseTileLayer = L.tileLayer.zoomify('/zoomify/borelis/{g}/{z}-{x}-{y}.jpg', {
width: mapWidth,
height: mapHeight,
}).addTo(map)
const layerGroups = {
players: L.layerGroup(),
capitals: L.layerGroup(),
cities: L.layerGroup(),
towns: L.layerGroup(),
landmarks: L.layerGroup(),
quests: L.layerGroup(),
}
/**
* Build all markers and their related info
*/
for (let i = 0; i < markers.length; i++) {
const m = markers[i]
const coords = [convertYToScale(m.markerCoords.y), convertXToScale(m.markerCoords.x)]
let markerIcon
let markerIconOverride
switch (m.group) {
case "capitals":
markerIcon = L.icon({
iconUrl: `/icon/castle.png`,
shadowUrl: `/icon/castle-shadow.png`,
iconSize: [25, 25],
shadowSize: [35, 30],
iconAnchor: [12.5, 8],
shadowAnchor: [11, 12]
})
break
case "cities":
markerIcon = L.icon({
iconUrl: `/icon/circle.png`,
iconSize: [12, 12],
})
break
case "landmarks":
markerIcon = L.icon({
iconUrl: `/icon/monument.png`,
shadowUrl: `/icon/monument-shadow.png`,
iconSize: [18, 18],
shadowSize: [32, 14],
iconAnchor: [12.5, 8],
shadowAnchor: [15, 6]
})
break
case "quests":
markerIcon = L.icon({
iconUrl: `/icon/flag.png`,
shadowUrl: `/icon/flag-shadow.png`,
iconSize: [20, 23],
shadowSize: [35, 30],
iconAnchor: [12.5, 8],
shadowAnchor: [11, 12]
})
break
case "towns":
default:
iconKey = "house"
break
}
// Handle icon override if it's specified
markerIconOverride = markerIcon
if (m.icon) {
markerIconOverride = L.icon({
iconUrl: `/icon/${m.icon}.png`,
shadowUrl: `/icon/${m.icon}-shadow.png`,
iconSize: [25, 25],
shadowSize: [35, 30],
iconAnchor: [12.5, 8],
shadowAnchor: [11, 12]
})
}
const marker = L.marker(coords, { icon: markerIconOverride }).addTo(map)
// Build popup content
let popupContent = ""
if (m.cover && m.coverAuthor && m.coverLink) {
const figureClass = m.coverPortrait ? 'portrait' : 'landscape';
const figureWidth = m.coverPortrait ? 210 : 420;
popupContent += `
<figure class="${figureClass}">
<img src="/images/cover/${m.cover}" alt="${m.title}" width="${figureWidth}" loading="lazy" />
<figcaption>
<cite>par <a href="${m.coverLink}" target="_blank">${m.coverAuthor}</a></cite>
</figcaption>
</figure>
`
}
if (m.link) {
popupContent += `
<a href="${m.link}" target="_blank" class="title">
${m.title}
</a>
`
} else {
popupContent += `
<strong class="title">
${m.title}
</strong>
`
}
if (m.description) {
popupContent += `<p>${m.description}</p>`
}
if (m.mapId) {
popupContent += `
<a href="${m.mapId}" class="title">
Voir la carte détaillée
</a>
`
}
marker.bindPopup(popupContent).openPopup()
const displayTooltip = m.group === 'capitals'
if (displayTooltip) {
L.tooltip({ permanent: true, direction: 'top', offset: [0, 2], content: m.title, className: "capital-name", opacity: 1 }).setLatLng(coords).addTo(map)
}
marker.addTo(layerGroups[m.group])
/**
* Fly to specific marker
*/
document.addEventListener(`fly-to-${m.title}`, (e) => {
map.flyTo(coords, flyToZoomLevel, { duration: flyToDuration })
setTimeout(() => {
marker.openPopup()
}, flyToDuration * 1000)
})
}
/**
* Add player's position marker
*/
if ( players && Object.keys(players).length > 0 ) {
const playersPosition =[
convertYToScale(players.markerCoords.y),
convertXToScale(players.markerCoords.x)
]
const playerIcon = L.icon({
iconUrl: `/icon/location-pin.png`,
shadowUrl: `/icon/location-pin-shadow.png`,
iconSize: [18, 24],
shadowSize: [20, 16],
iconAnchor: [11, 8],
shadowAnchor: [3, 0],
})
const playerMarker = L.marker(playersPosition, { icon: playerIcon }).addTo(map)
playerMarker.addTo(layerGroups['players'])
const popupContent = `
<strong class="title">
${players.title}
</strong>
<p>${players.description}</p>
`
playerMarker.bindPopup(popupContent).openPopup()
document.addEventListener('fly-to-players', () => {
map.flyTo(playersPosition, flyToZoomLevel, { duration: flyToDuration })
setTimeout(() => {
playerMarker.openPopup()
}, flyToDuration * 1000)
})
}
map.fitBounds(baseTileLayer.getBounds())
/**
* RULER
*/
/**
* Workaround specific to leaflet version 1.8.0
* See https://github.com/ljagis/leaflet-measure/issues/171 for details
*/
L.Control.Measure.include({
// set icon on the capture marker
_setCaptureMarkerIcon: function () {
// disable autopan
this._captureMarker.options.autoPanOnFocus = false
// default function
this._captureMarker.setIcon(
L.divIcon({
iconSize: this._map.getSize().multiplyBy(2)
})
)
},
})
const rulerDistanceRatio = 1.8
const rulerOptions = {
position: 'bottomleft',
primaryLengthUnit: 'kilometers',
primaryAreaUnit: null,
units: {
kilometers: {
factor: 0.00013 * rulerDistanceRatio,
display: 'Mètres',
},
}
}
const measureControl = L.control.measure(rulerOptions)
measureControl.addTo(map)
map.setMaxBounds(
[
[
baseTileLayer.getBounds().getSouth() - maxBoundsPadding,
baseTileLayer.getBounds().getWest() - maxBoundsPadding
],
[
baseTileLayer.getBounds().getNorth() + maxBoundsPadding,
baseTileLayer.getBounds().getEast() + maxBoundsPadding
]
]
)
/**
* CUSTOM MARKERS
*/
// List to be persisted in localStorage
let customMarkersList = []
/**
* Function that saves the customMarkersList object into the localStorage
* It also sends an event to refresh data on other components that use localStorage
*/
function saveCustomMarkers() {
localStorage.setItem('custom-markers-aldys', JSON.stringify(customMarkersList))
const refreshCustomMarkersEvent = new CustomEvent('refresh-custom-markers', { bubbles: true })
mapEl.dispatchEvent(refreshCustomMarkersEvent)
}
// Stores last map coords on context
map.addEventListener('contextmenu', storeLastHeldPositions)
map.addEventListener('click', storeLastHeldPositions)
function storeLastHeldPositions(e) {
localStorage.setItem('lastHeldXPosition', e.latlng.lng)
localStorage.setItem('lastHeldYPosition', e.latlng.lat)
}
/**
* Function that creates custom markers and places them on the map
* @param {string} markerTitle
*/
function addCustomMarker(markerTitle) {
const lon = Number(localStorage.getItem('lastHeldXPosition'))
const lat = Number(localStorage.getItem('lastHeldYPosition'))
const customMarkerIconOptions = {
iconUrl: `/icon/push-pin.png`,
shadowUrl: `/icon/push-pin-shadow.png`,
iconSize: [20, 20],
shadowSize: [30, 25],
iconAnchor: [0, 13],
shadowAnchor: [0, 15],
className: "fade-in"
}
const customMarkerIcon = L.icon(customMarkerIconOptions)
const customMarkerCoords = [lat, lon]
// Sticks the custom marker on the map
const customMarker = L.marker(customMarkerCoords, { icon: customMarkerIcon }).addTo(map)
if (markerTitle) {
const popupContent = `
${markerTitle}
`
customMarker.bindPopup(popupContent, { offset: [5, -5] }).openPopup()
}
/**
* Register event listener to fly to this custom marker
* TODO : Refactor these functions with the loadCustomMarkersFromStorage implementation
*/
document.addEventListener(`fly-to-${markerTitle}`, (e) => {
map.flyTo(customMarkerCoords, flyToZoomLevel, { duration: flyToDuration })
setTimeout(() => {
customMarker.openPopup()
}, flyToDuration * 1000)
})
/**
* Register event listener to remove this custom marker
* TODO : Refactor these functions with the loadCustomMarkersFromStorage implementation
*/
document.addEventListener(`delete-${markerTitle}`, () => {
removeCustomMarker(customMarker, markerTitle)
})
customMarkersList.push({
lat,
lon,
title: markerTitle,
icon: customMarkerIconOptions,
group: "custom"
})
saveCustomMarkers()
}
// Listeners for the custom add marker event
// See MarkerCreator.vue component for more details
document.addEventListener('add-custom-marker', (e) => {
addCustomMarker(e.detail.title)
})
/**
* Load all custom markers onto the map
*/
function loadCustomMarkersFromStorage() {
const customMarkersData = JSON.parse(localStorage.getItem('custom-markers-aldys'))
if (!customMarkersData) return
customMarkersList = customMarkersData
for (let i = 0; i < customMarkersData.length; i++) {
const m = customMarkersData[i];
const savedMarkerCoords = [m.lat, m.lon]
const savedMarker = L.marker(savedMarkerCoords, { icon: L.icon(m.icon) }).addTo(map)
if (m.title) {
const popupContent = `
${m.title}
`
savedMarker.bindPopup(popupContent, { offset: [5, -5] })
}
/**
* Register event listener to fly to this custom marker
* TODO : Refactor these functions with the addCustomMarker implementation
*/
document.addEventListener(`fly-to-${m.title}`, (e) => {
map.flyTo(savedMarkerCoords, flyToZoomLevel, { duration: flyToDuration })
setTimeout(() => {
savedMarker.openPopup()
}, flyToDuration * 1000)
})
/**
* Register event listener to remove this custom marker
* TODO : Refactor these functions with the addCustomMarker implementation
*/
document.addEventListener(`delete-${m.title}`, () => {
removeCustomMarker(savedMarker, m.title)
})
}
}
loadCustomMarkersFromStorage()
/**
* Function that deletes a specific marker from storage and removes it from the map
* @param {Marker} marker Leaflet marker object
* @param {string} markerTitle Key for custom marker
*/
function removeCustomMarker(marker, markerTitle) {
marker.remove()
customMarkersList = customMarkersList.filter(m => {
return m.title !== markerTitle
})
saveCustomMarkers()
}
/**
* URL PARAMETERS
*/
/**
* From the leaflet setup, get a working param config
*
* @returns Latitude, Longitude and zoom level of the map
*/
function getURLFromSetup() {
const centerOfMap = map.getCenter()
const lat = centerOfMap.lat
const lon = centerOfMap.lng
const zoom = parseFloat(map.getZoom().toFixed(1))
return { lat, lon, zoom }
}
/**
* Get leaflet coords from URL params
*
* @returns URLSearchParams
*/
function getCoordsSetupFromURL() {
const params = new URL(document.location).searchParams
return params
}
/**
* Check if the params in the URL can be parsed as leaflet coordinates
*
* @return boolean
*/
function hasInitialSetup() {
const params = new URL(document.location).searchParams
return params.has('lat') && params.has('lon')
}
/**
* Function to refresh the URL params
*
* @returns void
*/
function setupToURL() {
const setup = getURLFromSetup()
const newParams = new URLSearchParams(setup)
const newUrl = new URL(window.location.href)
newUrl.search = newParams.toString()
window.history.replaceState(null, '', newUrl.toString())
}
/**
* Uses the URL params to setup the leaflet map
*/
function setupFromURL() {
const setup = getCoordsSetupFromURL()
map.setView({ lat: setup.get('lat'), lng: setup.get('lon') }, setup.get('zoom') | minZoom, { animate: false })
}
// Check if the map has initial params in the URL
// If not, just setup the default URL
if (hasInitialSetup()) {
setupFromURL()
} else {
setupToURL()
}
// When the map stops moving, update the URL to keep it up to date
map.on('moveend', () => {
setupToURL()
})
/**
* UTILITIES
*/
/**
* Convert x coords to scale with ratio, zoom and offset
* @param {number} x
*/
function convertXToScale(x) {
return (x * xRatio * zoomRatio) + xOffset
}
/**
* Convert y coords to scale with ratio, zoom and offset
* @param {number} y
*/
function convertYToScale(y) {
return (y * yRatio * zoomRatio) + yOffset
}
/**
* Convert leaflet scale to x with ratio, zoom and offset
*
* It is basically the mathematically inverse function of convertXToScale()
* @param {number} y
*/
function convertScaleToX(x) {
return (x - xOffset) / (xRatio * zoomRatio)
}
/**
* Convert leaflet scale to y with ratio, zoom and offset
*
* It is basically the mathematically inverse function of convertYToScale()
* @param {number} y
*/
function convertScaleToY(y) {
return (y - yOffset) / (yRatio * zoomRatio)
}
/**
* DEBUGS
*/
/**
* Get relevant map info
*/
map.addEventListener('click', (event) => {
const lat = convertScaleToY(event.latlng.lat)
const lon = convertScaleToX(event.latlng.lng)
console.group('Point data')
console.log("Latitude :", lat)
console.log("Longitude :", lon)
console.log("Zoom :", map.getZoom())
console.groupEnd()
})
</script>
<style lang="scss">
html,
body {
height: 100vh;
margin: 0;
position: relative;
}
.world-wrapper {
position: fixed;
inset: 0;
z-index: 0;
isolation: isolate;
#world {
height: 100%;
width: 100%;
}
}
.leaflet-container {
aspect-ratio: 1551 / 1605;
background-color: #C3CCD2;
}
</style>

View File

@@ -1,637 +0,0 @@
---
import type { MapMarker, PlayerMarker } from '@/types/Leaflet';
import MarkerCreator from './overlay/MarkerCreator.vue';
const markers = [] as MapMarker[]
const players = {} as PlayerMarker
---
<div class="world-wrapper">
<div id="world"></div>
<MarkerCreator client:load mapKey="aldys" />
</div>
<script lang="ts" define:vars={{ markers, players }} defer>
const mapHeight = 4124; //
const mapWidth = 4067; // Find these in the zoomify / original image data
// Units used for convertions to avoid changing original marker coords
const xRatio = .68447
const yRatio = .68447
const zoomRatio = .75
const xOffset = 0
const yOffset = -27.63442
/**
* LEAFLET MAP PARAMERTERS
*/
const minZoom = 3.5
const maxZoom = 6.7 // Looks best with zoomify image
const flyToDuration = 1.2 // In seconds
const flyToZoomLevel = 6.5
const mobileZoomLevels = {
minZoom: 2.5,
maxZoom: 6.7
}
const inertiaDeceleration = 2000
const maxBoundsPadding = 10
const maxBoundsViscosity = .75
const mapEl = document.getElementById('world')
// Initializes the map
const map = L.map('world', {
crs: L.CRS.Simple,
maxZoom,
minZoom,
zoom: minZoom,
zoomSnap: 0,
zoomControl: false,
inertiaDeceleration,
maxBoundsViscosity
})
/**
* MOBILE JS
*/
// Breakpoints
const desktopMQ = window.matchMedia('(min-width: 900px)')
function handleMapResize(e) {
if (e.matches) {
map.setMaxZoom(maxZoom)
map.setMinZoom(minZoom)
} else {
map.setMaxZoom(mobileZoomLevels.maxZoom)
map.setMinZoom(mobileZoomLevels.minZoom)
}
}
desktopMQ.addEventListener('change', handleMapResize)
handleMapResize(desktopMQ)
// Immediately set center of map
L.control.zoom({
position: 'bottomright',
}).addTo(map)
// Layers
const baseTileLayer = L.tileLayer.zoomify('/zoomify/cantane/{g}/{z}-{x}-{y}.jpg', {
width: mapWidth,
height: mapHeight,
}).addTo(map)
const layerGroups = {
players: L.layerGroup(),
capitals: L.layerGroup(),
cities: L.layerGroup(),
towns: L.layerGroup(),
landmarks: L.layerGroup(),
quests: L.layerGroup(),
}
/**
* Build all markers and their related info
*/
for (let i = 0; i < markers.length; i++) {
const m = markers[i]
const coords = [convertYToScale(m.markerCoords.y), convertXToScale(m.markerCoords.x)]
let markerIcon
let markerIconOverride
switch (m.group) {
case "capitals":
markerIcon = L.icon({
iconUrl: `/icon/castle.png`,
shadowUrl: `/icon/castle-shadow.png`,
iconSize: [25, 25],
shadowSize: [35, 30],
iconAnchor: [12.5, 8],
shadowAnchor: [11, 12]
})
break
case "cities":
markerIcon = L.icon({
iconUrl: `/icon/circle.png`,
iconSize: [12, 12],
})
break
case "landmarks":
markerIcon = L.icon({
iconUrl: `/icon/monument.png`,
shadowUrl: `/icon/monument-shadow.png`,
iconSize: [18, 18],
shadowSize: [32, 14],
iconAnchor: [12.5, 8],
shadowAnchor: [15, 6]
})
break
case "quests":
markerIcon = L.icon({
iconUrl: `/icon/flag.png`,
shadowUrl: `/icon/flag-shadow.png`,
iconSize: [20, 23],
shadowSize: [35, 30],
iconAnchor: [12.5, 8],
shadowAnchor: [11, 12]
})
break
case "towns":
default:
iconKey = "house"
break
}
// Handle icon override if it's specified
markerIconOverride = markerIcon
if (m.icon) {
markerIconOverride = L.icon({
iconUrl: `/icon/${m.icon}.png`,
shadowUrl: `/icon/${m.icon}-shadow.png`,
iconSize: [25, 25],
shadowSize: [35, 30],
iconAnchor: [12.5, 8],
shadowAnchor: [11, 12]
})
}
const marker = L.marker(coords, { icon: markerIconOverride }).addTo(map)
// Build popup content
let popupContent = ""
if (m.cover && m.coverAuthor && m.coverLink) {
const figureClass = m.coverPortrait ? 'portrait' : 'landscape';
const figureWidth = m.coverPortrait ? 210 : 420;
popupContent += `
<figure class="${figureClass}">
<img src="/images/cover/${m.cover}" alt="${m.title}" width="${figureWidth}" loading="lazy" />
<figcaption>
<cite>par <a href="${m.coverLink}" target="_blank">${m.coverAuthor}</a></cite>
</figcaption>
</figure>
`
}
if (m.link) {
popupContent += `
<a href="${m.link}" target="_blank" class="title">
${m.title}
</a>
`
} else {
popupContent += `
<strong class="title">
${m.title}
</strong>
`
}
if (m.description) {
popupContent += `<p>${m.description}</p>`
}
if (m.mapId) {
popupContent += `
<a href="${m.mapId}" class="title">
Voir la carte détaillée
</a>
`
}
marker.bindPopup(popupContent).openPopup()
const displayTooltip = m.group === 'capitals'
if (displayTooltip) {
L.tooltip({ permanent: true, direction: 'top', offset: [0, 2], content: m.title, className: "capital-name", opacity: 1 }).setLatLng(coords).addTo(map)
}
marker.addTo(layerGroups[m.group])
/**
* Fly to specific marker
*/
document.addEventListener(`fly-to-${m.title}`, (e) => {
map.flyTo(coords, flyToZoomLevel, { duration: flyToDuration })
setTimeout(() => {
marker.openPopup()
}, flyToDuration * 1000)
})
}
/**
* Add player's position marker
*/
if ( players && Object.keys(players).length > 0 ) {
const playersPosition =[
convertYToScale(players.markerCoords.y),
convertXToScale(players.markerCoords.x)
]
const playerIcon = L.icon({
iconUrl: `/icon/location-pin.png`,
shadowUrl: `/icon/location-pin-shadow.png`,
iconSize: [18, 24],
shadowSize: [20, 16],
iconAnchor: [11, 8],
shadowAnchor: [3, 0],
})
const playerMarker = L.marker(playersPosition, { icon: playerIcon }).addTo(map)
playerMarker.addTo(layerGroups['players'])
const popupContent = `
<strong class="title">
${players.title}
</strong>
<p>${players.description}</p>
`
playerMarker.bindPopup(popupContent).openPopup()
document.addEventListener('fly-to-players', () => {
map.flyTo(playersPosition, flyToZoomLevel, { duration: flyToDuration })
setTimeout(() => {
playerMarker.openPopup()
}, flyToDuration * 1000)
})
}
map.fitBounds(baseTileLayer.getBounds())
/**
* RULER
*/
/**
* Workaround specific to leaflet version 1.8.0
* See https://github.com/ljagis/leaflet-measure/issues/171 for details
*/
L.Control.Measure.include({
// set icon on the capture marker
_setCaptureMarkerIcon: function () {
// disable autopan
this._captureMarker.options.autoPanOnFocus = false
// default function
this._captureMarker.setIcon(
L.divIcon({
iconSize: this._map.getSize().multiplyBy(2)
})
)
},
})
const rulerDistanceRatio = 1.15
const rulerOptions = {
position: 'bottomleft',
primaryLengthUnit: 'kilometers',
primaryAreaUnit: null,
units: {
kilometers: {
factor: 0.00013 * rulerDistanceRatio,
display: 'Mètres',
},
}
}
const measureControl = L.control.measure(rulerOptions)
measureControl.addTo(map)
map.setMaxBounds(
[
[
baseTileLayer.getBounds().getSouth() - maxBoundsPadding,
baseTileLayer.getBounds().getWest() - maxBoundsPadding
],
[
baseTileLayer.getBounds().getNorth() + maxBoundsPadding,
baseTileLayer.getBounds().getEast() + maxBoundsPadding
]
]
)
/**
* CUSTOM MARKERS
*/
// List to be persisted in localStorage
let customMarkersList = []
/**
* Function that saves the customMarkersList object into the localStorage
* It also sends an event to refresh data on other components that use localStorage
*/
function saveCustomMarkers() {
localStorage.setItem('custom-markers-aldys', JSON.stringify(customMarkersList))
const refreshCustomMarkersEvent = new CustomEvent('refresh-custom-markers', { bubbles: true })
mapEl.dispatchEvent(refreshCustomMarkersEvent)
}
// Stores last map coords on context
map.addEventListener('contextmenu', storeLastHeldPositions)
map.addEventListener('click', storeLastHeldPositions)
function storeLastHeldPositions(e) {
localStorage.setItem('lastHeldXPosition', e.latlng.lng)
localStorage.setItem('lastHeldYPosition', e.latlng.lat)
}
/**
* Function that creates custom markers and places them on the map
* @param {string} markerTitle
*/
function addCustomMarker(markerTitle) {
const lon = Number(localStorage.getItem('lastHeldXPosition'))
const lat = Number(localStorage.getItem('lastHeldYPosition'))
const customMarkerIconOptions = {
iconUrl: `/icon/push-pin.png`,
shadowUrl: `/icon/push-pin-shadow.png`,
iconSize: [20, 20],
shadowSize: [30, 25],
iconAnchor: [0, 13],
shadowAnchor: [0, 15],
className: "fade-in"
}
const customMarkerIcon = L.icon(customMarkerIconOptions)
const customMarkerCoords = [lat, lon]
// Sticks the custom marker on the map
const customMarker = L.marker(customMarkerCoords, { icon: customMarkerIcon }).addTo(map)
if (markerTitle) {
const popupContent = `
${markerTitle}
`
customMarker.bindPopup(popupContent, { offset: [5, -5] }).openPopup()
}
/**
* Register event listener to fly to this custom marker
* TODO : Refactor these functions with the loadCustomMarkersFromStorage implementation
*/
document.addEventListener(`fly-to-${markerTitle}`, (e) => {
map.flyTo(customMarkerCoords, flyToZoomLevel, { duration: flyToDuration })
setTimeout(() => {
customMarker.openPopup()
}, flyToDuration * 1000)
})
/**
* Register event listener to remove this custom marker
* TODO : Refactor these functions with the loadCustomMarkersFromStorage implementation
*/
document.addEventListener(`delete-${markerTitle}`, () => {
removeCustomMarker(customMarker, markerTitle)
})
customMarkersList.push({
lat,
lon,
title: markerTitle,
icon: customMarkerIconOptions,
group: "custom"
})
saveCustomMarkers()
}
// Listeners for the custom add marker event
// See MarkerCreator.vue component for more details
document.addEventListener('add-custom-marker', (e) => {
addCustomMarker(e.detail.title)
})
/**
* Load all custom markers onto the map
*/
function loadCustomMarkersFromStorage() {
const customMarkersData = JSON.parse(localStorage.getItem('custom-markers-aldys'))
if (!customMarkersData) return
customMarkersList = customMarkersData
for (let i = 0; i < customMarkersData.length; i++) {
const m = customMarkersData[i];
const savedMarkerCoords = [m.lat, m.lon]
const savedMarker = L.marker(savedMarkerCoords, { icon: L.icon(m.icon) }).addTo(map)
if (m.title) {
const popupContent = `
${m.title}
`
savedMarker.bindPopup(popupContent, { offset: [5, -5] })
}
/**
* Register event listener to fly to this custom marker
* TODO : Refactor these functions with the addCustomMarker implementation
*/
document.addEventListener(`fly-to-${m.title}`, (e) => {
map.flyTo(savedMarkerCoords, flyToZoomLevel, { duration: flyToDuration })
setTimeout(() => {
savedMarker.openPopup()
}, flyToDuration * 1000)
})
/**
* Register event listener to remove this custom marker
* TODO : Refactor these functions with the addCustomMarker implementation
*/
document.addEventListener(`delete-${m.title}`, () => {
removeCustomMarker(savedMarker, m.title)
})
}
}
loadCustomMarkersFromStorage()
/**
* Function that deletes a specific marker from storage and removes it from the map
* @param {Marker} marker Leaflet marker object
* @param {string} markerTitle Key for custom marker
*/
function removeCustomMarker(marker, markerTitle) {
marker.remove()
customMarkersList = customMarkersList.filter(m => {
return m.title !== markerTitle
})
saveCustomMarkers()
}
/**
* URL PARAMETERS
*/
/**
* From the leaflet setup, get a working param config
*
* @returns Latitude, Longitude and zoom level of the map
*/
function getURLFromSetup() {
const centerOfMap = map.getCenter()
const lat = centerOfMap.lat
const lon = centerOfMap.lng
const zoom = parseFloat(map.getZoom().toFixed(1))
return { lat, lon, zoom }
}
/**
* Get leaflet coords from URL params
*
* @returns URLSearchParams
*/
function getCoordsSetupFromURL() {
const params = new URL(document.location).searchParams
return params
}
/**
* Check if the params in the URL can be parsed as leaflet coordinates
*
* @return boolean
*/
function hasInitialSetup() {
const params = new URL(document.location).searchParams
return params.has('lat') && params.has('lon')
}
/**
* Function to refresh the URL params
*
* @returns void
*/
function setupToURL() {
const setup = getURLFromSetup()
const newParams = new URLSearchParams(setup)
const newUrl = new URL(window.location.href)
newUrl.search = newParams.toString()
window.history.replaceState(null, '', newUrl.toString())
}
/**
* Uses the URL params to setup the leaflet map
*/
function setupFromURL() {
const setup = getCoordsSetupFromURL()
map.setView({ lat: setup.get('lat'), lng: setup.get('lon') }, setup.get('zoom') | minZoom, { animate: false })
}
// Check if the map has initial params in the URL
// If not, just setup the default URL
if (hasInitialSetup()) {
setupFromURL()
} else {
setupToURL()
}
// When the map stops moving, update the URL to keep it up to date
map.on('moveend', () => {
setupToURL()
})
/**
* UTILITIES
*/
/**
* Convert x coords to scale with ratio, zoom and offset
* @param {number} x
*/
function convertXToScale(x) {
return (x * xRatio * zoomRatio) + xOffset
}
/**
* Convert y coords to scale with ratio, zoom and offset
* @param {number} y
*/
function convertYToScale(y) {
return (y * yRatio * zoomRatio) + yOffset
}
/**
* Convert leaflet scale to x with ratio, zoom and offset
*
* It is basically the mathematically inverse function of convertXToScale()
* @param {number} y
*/
function convertScaleToX(x) {
return (x - xOffset) / (xRatio * zoomRatio)
}
/**
* Convert leaflet scale to y with ratio, zoom and offset
*
* It is basically the mathematically inverse function of convertYToScale()
* @param {number} y
*/
function convertScaleToY(y) {
return (y - yOffset) / (yRatio * zoomRatio)
}
/**
* DEBUGS
*/
/**
* Get relevant map info
*/
map.addEventListener('click', (event) => {
const lat = convertScaleToY(event.latlng.lat)
const lon = convertScaleToX(event.latlng.lng)
console.group('Point data')
console.log("Latitude :", lat)
console.log("Longitude :", lon)
console.log("Zoom :", map.getZoom())
console.groupEnd()
})
</script>
<style lang="scss">
html,
body {
height: 100vh;
margin: 0;
position: relative;
}
.world-wrapper {
position: fixed;
inset: 0;
z-index: 0;
isolation: isolate;
#world {
height: 100%;
width: 100%;
}
}
.leaflet-container {
aspect-ratio: 1551 / 1605;
background-color: #CDCAC3;
}
</style>

View File

@@ -1,25 +0,0 @@
---
import type { MapMarker, PlayerMarker } from "@/types/Leaflet";
import SearchMarkers from "./overlay/SearchMarkers.vue";
const markers = [] as MapMarker[]
const players = {} as PlayerMarker
---
<div class="world-overlay">
<SearchMarkers client:load markers={markers} players={players} mapKey="aldys-cantane" />
</div>
<style lang="scss">
.world-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
z-index: 10;
padding: 10px;
pointer-events: none;
}
</style>

View File

@@ -1,24 +1,37 @@
--- ---
import type { MapMarker, PlayerMarker } from '@/types/Leaflet'; import type { MapMarker, PlayerMarker } from '@/types/Leaflet';
import markersData from '@/components/maps/data/aldys/markers.json'
import playersData from '@/components/maps/data/aldys/players.json'
import MarkerCreator from './overlay/MarkerCreator.vue'; import MarkerCreator from './overlay/MarkerCreator.vue';
const markers = markersData.data as MapMarker[] interface Props {
const players = playersData as PlayerMarker mapKey: string
zoomifyKey: string
mapHeight: number
mapWidth: number
markers?: MapMarker[]
players?: PlayerMarker
rulerDistanceRatio?: number
backgroundColor?: string
}
const {
mapKey,
zoomifyKey,
mapHeight, //
mapWidth, // Find these in the zoomify / original image data
markers = [] as MapMarker[],
players = {} as PlayerMarker,
rulerDistanceRatio = 1,
backgroundColor = 'transparent',
} = Astro.props
--- ---
<div class="world-wrapper"> <div class="world-wrapper">
<div id="world"></div> <div id="world"></div>
<MarkerCreator client:load mapKey="aldys" /> <MarkerCreator client:load mapKey={mapKey} />
</div> </div>
<script lang="ts" define:vars={{ markers, players }} defer> <script lang="ts" define:vars={{ mapKey, rulerDistanceRatio, zoomifyKey, markers, players, mapHeight, mapWidth }} defer>
const mapHeight = 21896; //
const mapWidth = 30000; // Find these in the zoomify / original image data
// Units used for convertions to avoid changing original marker coords // Units used for convertions to avoid changing original marker coords
const xRatio = .68447 const xRatio = .68447
@@ -83,7 +96,7 @@ L.control.zoom({
}).addTo(map) }).addTo(map)
// Layers // Layers
const baseTileLayer = L.tileLayer.zoomify('/zoomify/aldys-nord/{g}/{z}-{x}-{y}.jpg', { const baseTileLayer = L.tileLayer.zoomify(`/zoomify/${zoomifyKey}/{g}/{z}-{x}-{y}.jpg`, {
width: mapWidth, width: mapWidth,
height: mapHeight, height: mapHeight,
}).addTo(map) }).addTo(map)
@@ -298,8 +311,6 @@ L.Control.Measure.include({
}, },
}) })
const rulerDistanceRatio = 2
const rulerOptions = { const rulerOptions = {
position: 'bottomleft', position: 'bottomleft',
primaryLengthUnit: 'kilometers', primaryLengthUnit: 'kilometers',
@@ -346,7 +357,7 @@ let customMarkersList = []
* It also sends an event to refresh data on other components that use localStorage * It also sends an event to refresh data on other components that use localStorage
*/ */
function saveCustomMarkers() { function saveCustomMarkers() {
localStorage.setItem('custom-markers-aldys', JSON.stringify(customMarkersList)) localStorage.setItem(`custom-markers-${mapKey}`, JSON.stringify(customMarkersList))
const refreshCustomMarkersEvent = new CustomEvent('refresh-custom-markers', { bubbles: true }) const refreshCustomMarkersEvent = new CustomEvent('refresh-custom-markers', { bubbles: true })
@@ -432,7 +443,7 @@ document.addEventListener('add-custom-marker', (e) => {
* Load all custom markers onto the map * Load all custom markers onto the map
*/ */
function loadCustomMarkersFromStorage() { function loadCustomMarkersFromStorage() {
const customMarkersData = JSON.parse(localStorage.getItem('custom-markers-aldys')) const customMarkersData = JSON.parse(localStorage.getItem(`custom-markers-${mapKey}`))
if (!customMarkersData) return if (!customMarkersData) return
@@ -621,7 +632,7 @@ map.addEventListener('click', (event) => {
}) })
</script> </script>
<style lang="scss"> <style lang="scss" define:vars={{ backgroundColor }}>
html, html,
body { body {
height: 100vh; height: 100vh;
@@ -643,5 +654,6 @@ body {
.leaflet-container { .leaflet-container {
aspect-ratio: 1551 / 1605; aspect-ratio: 1551 / 1605;
background-color: var(--backgroundColor);
} }
</style> </style>

View File

@@ -3,12 +3,21 @@ import type { MapMarker, PlayerMarker } from "@/types/Leaflet";
import SearchMarkers from "./overlay/SearchMarkers.vue"; import SearchMarkers from "./overlay/SearchMarkers.vue";
const markers = [] as MapMarker[] interface Props {
const players = {} as PlayerMarker mapKey: string
markers?: MapMarker[]
players?: PlayerMarker
}
const {
mapKey,
markers = [] as MapMarker[],
players = {} as PlayerMarker,
} = Astro.props
--- ---
<div class="world-overlay"> <div class="world-overlay">
<SearchMarkers client:load markers={markers} players={players} mapKey="aldys-borelis" /> <SearchMarkers client:load mapKey={mapKey} markers={markers} players={players} />
</div> </div>
<style lang="scss"> <style lang="scss">
@@ -22,4 +31,4 @@ const players = {} as PlayerMarker
padding: 10px; padding: 10px;
pointer-events: none; pointer-events: none;
} }
</style> </style>

View File

@@ -1,10 +1,19 @@
--- ---
import Layout from '../../../layouts/Layout.astro'; import Layout from '@/layouts/Layout.astro';
import BorelisMap from '../../../components/maps/BorelisMap.astro' import Map from '@/components/maps/Map.astro'
import BorelisMapOverlay from '../../../components/maps/BorelisMapOverlay.astro' import MapOverlay from '@/components/maps/MapOverlay.astro'
--- ---
<Layout title="Borélis ~ Cartographie"> <Layout title="Borélis ~ Cartographie">
<BorelisMapOverlay /> <MapOverlay
<BorelisMap /> mapKey='aldys-borelis'
/>
<Map
mapKey='aldys-borelis'
zoomifyKey='borelis'
mapHeight={3710}
mapWidth={4521}
rulerDistanceRatio={1.8}
backgroundColor="#C3CCD2"
/>
</Layout> </Layout>

View File

@@ -1,10 +1,19 @@
--- ---
import Layout from '../../../layouts/Layout.astro'; import Layout from '@/layouts/Layout.astro';
import CantaneMap from '../../../components/maps/CantaneMap.astro' import Map from '@/components/maps/Map.astro'
import CantaneMapOverlay from '../../../components/maps/CantaneMapOverlay.astro' import MapOverlay from '@/components/maps/MapOverlay.astro'
--- ---
<Layout title="Cantane ~ Cartographie"> <Layout title="Cantane ~ Cartographie">
<CantaneMapOverlay /> <MapOverlay
<CantaneMap /> mapKey='aldys-cantane'
/>
<Map
mapKey='aldys-cantane'
zoomifyKey='cantane'
mapHeight={4124}
mapWidth={4067}
rulerDistanceRatio={1.15}
backgroundColor="#CDCAC3"
/>
</Layout> </Layout>

View File

@@ -1,10 +1,31 @@
--- ---
import Layout from '../../layouts/Layout.astro'; import type { MapMarker, PlayerMarker } from '@/types/Leaflet';
import BamastMap from '../../components/maps/BamastMap.astro'
import BamastMapOverlay from '../../components/maps/BamastMapOverlay.astro' import Layout from '@/layouts/Layout.astro';
import Map from '@/components/maps/Map.astro'
import MapOverlay from '@/components/maps/MapOverlay.astro'
import markersData from '@/components/maps/data/bamast/markers.json'
import playersData from '@/components/maps/data/bamast/players.json'
const markers = markersData.data as MapMarker[]
const players = playersData as PlayerMarker
--- ---
<Layout title="Bamast ~ Cartographie"> <Layout title="Bamast ~ Cartographie">
<BamastMapOverlay /> <MapOverlay
<BamastMap /> mapKey='bamast'
markers={markers}
players={players}
/>
<Map
mapKey='bamast'
zoomifyKey='bamast'
mapHeight={18342}
mapWidth={15000}
markers={markers}
players={players}
rulerDistanceRatio={2}
backgroundColor="#98BDD0"
/>
</Layout> </Layout>

View File

@@ -1,10 +1,31 @@
--- ---
import Layout from '../layouts/Layout.astro'; import type { MapMarker, PlayerMarker } from '@/types/Leaflet';
import AldysMap from '../components/maps/AldysMap.astro'
import AldysMapOverlay from '../components/maps/AldysMapOverlay.astro' import Layout from '@/layouts/Layout.astro';
import Map from '@/components/maps/Map.astro'
import MapOverlay from '@/components/maps/MapOverlay.astro'
import markersData from '@/components/maps/data/aldys/markers.json'
import playersData from '@/components/maps/data/aldys/players.json'
const markers = markersData.data as MapMarker[]
const players = playersData as PlayerMarker
--- ---
<Layout title="Aldys ~ Cartographie"> <Layout title="Aldys ~ Cartographie">
<AldysMapOverlay /> <MapOverlay
<AldysMap /> mapKey='aldys'
markers={markers}
players={players}
/>
<Map
mapKey='aldys'
zoomifyKey='aldys-nord'
mapHeight={21896}
mapWidth={30000}
markers={markers}
players={players}
rulerDistanceRatio={2}
backgroundColor="#98BDD0"
/>
</Layout> </Layout>