Unified maps with a single component
This commit is contained in:
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user